node_modules ignore

This commit is contained in:
oier committed 2025-05-08 23:43:47 +02:00
1 parent e19d52f172
commit 4574544c9f
65041 files changed
+10593536

No files matched your search

@@ -0,0 +1,6 @@
'use strict';
var img = "data:image/svg+xml,%3csvg width='1440' height='810' viewBox='0 0 1440 810' fill='none' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'%3e%3cg clip-path='url(%23clip0_347_14228)'%3e%3crect width='1440' height='810' fill='%23181826'/%3e%3cg filter='url(%23filter0_d_347_14228)'%3e%3cg clip-path='url(%23clip1_347_14228)'%3e%3crect x='80' y='80' width='1440' height='900' rx='4' fill='white'/%3e%3cg clip-path='url(%23clip2_347_14228)'%3e%3cmask id='path-4-inside-1_347_14228' fill='white'%3e%3cpath d='M80 80H136V980H80V80Z'/%3e%3c/mask%3e%3cpath d='M80 80H136V980H80V80Z' fill='%23212134'/%3e%3cpath d='M135 80V980H137V80H135Z' fill='%2332324D' mask='url(%23path-4-inside-1_347_14228)'/%3e%3cmask id='path-6-inside-2_347_14228' fill='white'%3e%3cpath d='M80 80H136V136H80V80Z'/%3e%3c/mask%3e%3cpath d='M136 135H80V137H136V135Z' fill='%2332324D' mask='url(%23path-6-inside-2_347_14228)'/%3e%3crect x='96' y='96' width='24' height='24' rx='4' fill='url(%23pattern0_347_14228)'/%3e%3cpath d='M115.5 163.027V170.25C115.5 170.582 115.368 170.899 115.134 171.134C114.899 171.368 114.582 171.5 114.25 171.5H111.125C110.793 171.5 110.476 171.368 110.241 171.134C110.007 170.899 109.875 170.582 109.875 170.25V167.125C109.875 166.959 109.809 166.8 109.692 166.683C109.575 166.566 109.416 166.5 109.25 166.5H106.75C106.584 166.5 106.425 166.566 106.308 166.683C106.191 166.8 106.125 166.959 106.125 167.125V170.25C106.125 170.582 105.993 170.899 105.759 171.134C105.524 171.368 105.207 171.5 104.875 171.5H101.75C101.418 171.5 101.101 171.368 100.866 171.134C100.632 170.899 100.5 170.582 100.5 170.25V163.027C100.5 162.854 100.536 162.683 100.605 162.525C100.675 162.366 100.777 162.224 100.904 162.107L107.154 156.21L107.162 156.202C107.393 155.992 107.692 155.876 108.004 155.876C108.315 155.876 108.614 155.992 108.845 156.202C108.847 156.205 108.85 156.207 108.853 156.21L115.103 162.107C115.229 162.225 115.33 162.367 115.398 162.526C115.466 162.684 115.501 162.855 115.5 163.027Z' fill='%238E8EA9'/%3e%3cpath d='M114.55 208.532L109.879 213.256C109.764 213.372 109.628 213.464 109.478 213.527C109.328 213.589 109.167 213.622 109.004 213.622H103.884L101.567 215.942C101.509 216 101.44 216.046 101.364 216.078C101.288 216.109 101.207 216.125 101.125 216.125C101.043 216.125 100.962 216.109 100.886 216.078C100.81 216.046 100.741 216 100.683 215.942C100.625 215.884 100.579 215.815 100.547 215.739C100.516 215.663 100.5 215.582 100.5 215.5C100.5 215.418 100.516 215.337 100.547 215.261C100.579 215.185 100.625 215.116 100.683 215.058L102.448 213.294L107.742 208H114.328C114.39 208 114.45 208.018 114.501 208.053C114.552 208.087 114.592 208.135 114.616 208.192C114.64 208.249 114.646 208.312 114.634 208.372C114.623 208.433 114.593 208.488 114.55 208.532ZM114.93 200.388C113.971 199.599 112.752 199.195 111.512 199.255C110.271 199.315 109.097 199.834 108.219 200.712L107.469 201.452C107.41 201.51 107.376 201.589 107.375 201.672V206.594L111.672 202.297C111.79 202.185 111.947 202.123 112.11 202.125C112.273 202.127 112.429 202.193 112.544 202.308C112.66 202.423 112.725 202.579 112.727 202.742C112.729 202.905 112.668 203.062 112.555 203.18L108.993 206.75H115.898C115.955 206.75 116.011 206.734 116.06 206.705C116.109 206.675 116.149 206.632 116.176 206.581C116.711 205.563 116.88 204.391 116.653 203.263C116.426 202.135 115.817 201.12 114.93 200.388ZM102.909 211.065L106.125 207.848V203.528C106.125 203.467 106.107 203.406 106.072 203.355C106.038 203.304 105.99 203.264 105.933 203.24C105.876 203.217 105.813 203.21 105.753 203.222C105.692 203.234 105.637 203.263 105.593 203.306L102.741 206.125C102.625 206.241 102.533 206.378 102.47 206.53C102.407 206.682 102.375 206.844 102.375 207.009V210.844C102.375 210.906 102.393 210.966 102.428 211.017C102.462 211.069 102.511 211.109 102.568 211.133C102.625 211.156 102.688 211.163 102.749 211.15C102.809 211.138 102.865 211.109 102.909 211.065Z' fill='%238E8EA9'/%3e%3crect x='92' y='236' width='32' height='32' rx='4' fill='%23181826'/%3e%3cpath d='M116.078 245.464C116.078 245.464 116.078 245.472 116.078 245.476L111.531 260.471C111.462 260.715 111.321 260.931 111.125 261.092C110.93 261.253 110.69 261.35 110.438 261.37C110.402 261.373 110.366 261.375 110.33 261.375C110.093 261.376 109.862 261.309 109.662 261.182C109.462 261.056 109.303 260.874 109.203 260.66L106.413 254.77C106.386 254.712 106.377 254.647 106.388 254.583C106.399 254.52 106.429 254.461 106.475 254.416L110.948 249.941C111.061 249.823 111.122 249.666 111.12 249.503C111.118 249.34 111.053 249.184 110.937 249.069C110.822 248.954 110.666 248.888 110.503 248.886C110.34 248.884 110.183 248.946 110.065 249.058L105.584 253.531C105.539 253.577 105.48 253.607 105.417 253.618C105.353 253.629 105.288 253.621 105.23 253.593L99.3704 250.817C99.1402 250.712 98.9466 250.54 98.8139 250.325C98.6812 250.109 98.6155 249.859 98.6251 249.606C98.6378 249.346 98.7319 249.097 98.8941 248.893C99.0563 248.69 99.2784 248.542 99.529 248.472L114.524 243.925H114.536C11Line truncated
module.exports = img;
//# sourceMappingURL=purchase-page-illustration-dark.svg.js.map
@@ -0,0 +1 @@
{"version":3,"file":"purchase-page-illustration-dark.svg.js","sources":["../../../admin/src/assets/purchase-page-illustration-dark.svg"],"sourcesContent":["var img = \"data:image/svg+xml,%3csvg width='1440' height='810' viewBox='0 0 1440 810' fill='none' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'%3e%3cg clip-path='url(%23clip0_347_14228)'%3e%3crect width='1440' height='810' fill='%23181826'/%3e%3cg filter='url(%23filter0_d_347_14228)'%3e%3cg clip-path='url(%23clip1_347_14228)'%3e%3crect x='80' y='80' width='1440' height='900' rx='4' fill='white'/%3e%3cg clip-path='url(%23clip2_347_14228)'%3e%3cmask id='path-4-inside-1_347_14228' fill='white'%3e%3cpath d='M80 80H136V980H80V80Z'/%3e%3c/mask%3e%3cpath d='M80 80H136V980H80V80Z' fill='%23212134'/%3e%3cpath d='M135 80V980H137V80H135Z' fill='%2332324D' mask='url(%23path-4-inside-1_347_14228)'/%3e%3cmask id='path-6-inside-2_347_14228' fill='white'%3e%3cpath d='M80 80H136V136H80V80Z'/%3e%3c/mask%3e%3cpath d='M136 135H80V137H136V135Z' fill='%2332324D' mask='url(%23path-6-inside-2_347_14228)'/%3e%3crect x='96' y='96' width='24' height='24' rx='4' fill='url(%23pattern0_347_14228)'/%3e%3cpath d='M115.5 163.027V170.25C115.5 170.582 115.368 170.899 115.134 171.134C114.899 171.368 114.582 171.5 114.25 171.5H111.125C110.793 171.5 110.476 171.368 110.241 171.134C110.007 170.899 109.875 170.582 109.875 170.25V167.125C109.875 166.959 109.809 166.8 109.692 166.683C109.575 166.566 109.416 166.5 109.25 166.5H106.75C106.584 166.5 106.425 166.566 106.308 166.683C106.191 166.8 106.125 166.959 106.125 167.125V170.25C106.125 170.582 105.993 170.899 105.759 171.134C105.524 171.368 105.207 171.5 104.875 171.5H101.75C101.418 171.5 101.101 171.368 100.866 171.134C100.632 170.899 100.5 170.582 100.5 170.25V163.027C100.5 162.854 100.536 162.683 100.605 162.525C100.675 162.366 100.777 162.224 100.904 162.107L107.154 156.21L107.162 156.202C107.393 155.992 107.692 155.876 108.004 155.876C108.315 155.876 108.614 155.992 108.845 156.202C108.847 156.205 108.85 156.207 108.853 156.21L115.103 162.107C115.229 162.225 115.33 162.367 115.398 162.526C115.466 162.684 115.501 162.855 115.5 163.027Z' fill='%238E8EA9'/%3e%3cpath d='M114.55 208.532L109.879 213.256C109.764 213.372 109.628 213.464 109.478 213.527C109.328 213.589 109.167 213.622 109.004 213.622H103.884L101.567 215.942C101.509 216 101.44 216.046 101.364 216.078C101.288 216.109 101.207 216.125 101.125 216.125C101.043 216.125 100.962 216.109 100.886 216.078C100.81 216.046 100.741 216 100.683 215.942C100.625 215.884 100.579 215.815 100.547 215.739C100.516 215.663 100.5 215.582 100.5 215.5C100.5 215.418 100.516 215.337 100.547 215.261C100.579 215.185 100.625 215.116 100.683 215.058L102.448 213.294L107.742 208H114.328C114.39 208 114.45 208.018 114.501 208.053C114.552 208.087 114.592 208.135 114.616 208.192C114.64 208.249 114.646 208.312 114.634 208.372C114.623 208.433 114.593 208.488 114.55 208.532ZM114.93 200.388C113.971 199.599 112.752 199.195 111.512 199.255C110.271 199.315 109.097 199.834 108.219 200.712L107.469 201.452C107.41 201.51 107.376 201.589 107.375 201.672V206.594L111.672 202.297C111.79 202.185 111.947 202.123 112.11 202.125C112.273 202.127 112.429 202.193 112.544 202.308C112.66 202.423 112.725 202.579 112.727 202.742C112.729 202.905 112.668 203.062 112.555 203.18L108.993 206.75H115.898C115.955 206.75 116.011 206.734 116.06 206.705C116.109 206.675 116.149 206.632 116.176 206.581C116.711 205.563 116.88 204.391 116.653 203.263C116.426 202.135 115.817 201.12 114.93 200.388ZM102.909 211.065L106.125 207.848V203.528C106.125 203.467 106.107 203.406 106.072 203.355C106.038 203.304 105.99 203.264 105.933 203.24C105.876 203.217 105.813 203.21 105.753 203.222C105.692 203.234 105.637 203.263 105.593 203.306L102.741 206.125C102.625 206.241 102.533 206.378 102.47 206.53C102.407 206.682 102.375 206.844 102.375 207.009V210.844C102.375 210.906 102.393 210.966 102.428 211.017C102.462 211.069 102.511 211.109 102.568 211.133C102.625 211.156 102.688 211.163 102.749 211.15C102.809 211.138 102.865 211.109 102.909 211.065Z' fill='%238E8EA9'/%3e%3crect x='92' y='236' width='32' height='32' rx='4' fill='%23181826'/%3e%3cpath d='M116.078 245.464C116.078 245.464 116.078 245.472 116.078 245.476L111.531 260.471C111.462 260.715 111.321 260.931 111.125 261.092C110.93 261.253 110.69 261.35 110.438 261.37C110.402 261.373 110.366 261.375 110.33 261.375C110.093 261.376 109.862 261.309 109.662 261.182C109.462 261.056 109.303 260.874 109.203 260.66L106.413 254.77C106.386 254.712 106.377 254.647 106.388 254.583C106.399 254.52 106.429 254.461 106.475 254.416L110.948 249.941C111.061 249.823 111.122 249.666 111.12 249.503C111.118 249.34 111.053 249.184 110.937 249.069C110.822 248.954 110.666 248.888 110.503 248.886C110.34 248.884 110.183 248.946 110.065 249.058L105.584 253.531C105.539 253.577 105.48 253.607 105.417 253.618C105.353 253.629 105.288 253.621 105.23 253.593L99.3704 250.817C99.1402 250.712 98.9466 250.54 98.8139 250.325C98.6812 250Line truncated
@@ -0,0 +1,4 @@
var img = "data:image/svg+xml,%3csvg width='1440' height='810' viewBox='0 0 1440 810' fill='none' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'%3e%3cg clip-path='url(%23clip0_347_14228)'%3e%3crect width='1440' height='810' fill='%23181826'/%3e%3cg filter='url(%23filter0_d_347_14228)'%3e%3cg clip-path='url(%23clip1_347_14228)'%3e%3crect x='80' y='80' width='1440' height='900' rx='4' fill='white'/%3e%3cg clip-path='url(%23clip2_347_14228)'%3e%3cmask id='path-4-inside-1_347_14228' fill='white'%3e%3cpath d='M80 80H136V980H80V80Z'/%3e%3c/mask%3e%3cpath d='M80 80H136V980H80V80Z' fill='%23212134'/%3e%3cpath d='M135 80V980H137V80H135Z' fill='%2332324D' mask='url(%23path-4-inside-1_347_14228)'/%3e%3cmask id='path-6-inside-2_347_14228' fill='white'%3e%3cpath d='M80 80H136V136H80V80Z'/%3e%3c/mask%3e%3cpath d='M136 135H80V137H136V135Z' fill='%2332324D' mask='url(%23path-6-inside-2_347_14228)'/%3e%3crect x='96' y='96' width='24' height='24' rx='4' fill='url(%23pattern0_347_14228)'/%3e%3cpath d='M115.5 163.027V170.25C115.5 170.582 115.368 170.899 115.134 171.134C114.899 171.368 114.582 171.5 114.25 171.5H111.125C110.793 171.5 110.476 171.368 110.241 171.134C110.007 170.899 109.875 170.582 109.875 170.25V167.125C109.875 166.959 109.809 166.8 109.692 166.683C109.575 166.566 109.416 166.5 109.25 166.5H106.75C106.584 166.5 106.425 166.566 106.308 166.683C106.191 166.8 106.125 166.959 106.125 167.125V170.25C106.125 170.582 105.993 170.899 105.759 171.134C105.524 171.368 105.207 171.5 104.875 171.5H101.75C101.418 171.5 101.101 171.368 100.866 171.134C100.632 170.899 100.5 170.582 100.5 170.25V163.027C100.5 162.854 100.536 162.683 100.605 162.525C100.675 162.366 100.777 162.224 100.904 162.107L107.154 156.21L107.162 156.202C107.393 155.992 107.692 155.876 108.004 155.876C108.315 155.876 108.614 155.992 108.845 156.202C108.847 156.205 108.85 156.207 108.853 156.21L115.103 162.107C115.229 162.225 115.33 162.367 115.398 162.526C115.466 162.684 115.501 162.855 115.5 163.027Z' fill='%238E8EA9'/%3e%3cpath d='M114.55 208.532L109.879 213.256C109.764 213.372 109.628 213.464 109.478 213.527C109.328 213.589 109.167 213.622 109.004 213.622H103.884L101.567 215.942C101.509 216 101.44 216.046 101.364 216.078C101.288 216.109 101.207 216.125 101.125 216.125C101.043 216.125 100.962 216.109 100.886 216.078C100.81 216.046 100.741 216 100.683 215.942C100.625 215.884 100.579 215.815 100.547 215.739C100.516 215.663 100.5 215.582 100.5 215.5C100.5 215.418 100.516 215.337 100.547 215.261C100.579 215.185 100.625 215.116 100.683 215.058L102.448 213.294L107.742 208H114.328C114.39 208 114.45 208.018 114.501 208.053C114.552 208.087 114.592 208.135 114.616 208.192C114.64 208.249 114.646 208.312 114.634 208.372C114.623 208.433 114.593 208.488 114.55 208.532ZM114.93 200.388C113.971 199.599 112.752 199.195 111.512 199.255C110.271 199.315 109.097 199.834 108.219 200.712L107.469 201.452C107.41 201.51 107.376 201.589 107.375 201.672V206.594L111.672 202.297C111.79 202.185 111.947 202.123 112.11 202.125C112.273 202.127 112.429 202.193 112.544 202.308C112.66 202.423 112.725 202.579 112.727 202.742C112.729 202.905 112.668 203.062 112.555 203.18L108.993 206.75H115.898C115.955 206.75 116.011 206.734 116.06 206.705C116.109 206.675 116.149 206.632 116.176 206.581C116.711 205.563 116.88 204.391 116.653 203.263C116.426 202.135 115.817 201.12 114.93 200.388ZM102.909 211.065L106.125 207.848V203.528C106.125 203.467 106.107 203.406 106.072 203.355C106.038 203.304 105.99 203.264 105.933 203.24C105.876 203.217 105.813 203.21 105.753 203.222C105.692 203.234 105.637 203.263 105.593 203.306L102.741 206.125C102.625 206.241 102.533 206.378 102.47 206.53C102.407 206.682 102.375 206.844 102.375 207.009V210.844C102.375 210.906 102.393 210.966 102.428 211.017C102.462 211.069 102.511 211.109 102.568 211.133C102.625 211.156 102.688 211.163 102.749 211.15C102.809 211.138 102.865 211.109 102.909 211.065Z' fill='%238E8EA9'/%3e%3crect x='92' y='236' width='32' height='32' rx='4' fill='%23181826'/%3e%3cpath d='M116.078 245.464C116.078 245.464 116.078 245.472 116.078 245.476L111.531 260.471C111.462 260.715 111.321 260.931 111.125 261.092C110.93 261.253 110.69 261.35 110.438 261.37C110.402 261.373 110.366 261.375 110.33 261.375C110.093 261.376 109.862 261.309 109.662 261.182C109.462 261.056 109.303 260.874 109.203 260.66L106.413 254.77C106.386 254.712 106.377 254.647 106.388 254.583C106.399 254.52 106.429 254.461 106.475 254.416L110.948 249.941C111.061 249.823 111.122 249.666 111.12 249.503C111.118 249.34 111.053 249.184 110.937 249.069C110.822 248.954 110.666 248.888 110.503 248.886C110.34 248.884 110.183 248.946 110.065 249.058L105.584 253.531C105.539 253.577 105.48 253.607 105.417 253.618C105.353 253.629 105.288 253.621 105.23 253.593L99.3704 250.817C99.1402 250.712 98.9466 250.54 98.8139 250.325C98.6812 250.109 98.6155 249.859 98.6251 249.606C98.6378 249.346 98.7319 249.097 98.8941 248.893C99.0563 248.69 99.2784 248.542 99.529 248.472L114.524 243.925H114.536C11Line truncated
export { img as default };
//# sourceMappingURL=purchase-page-illustration-dark.svg.mjs.map
@@ -0,0 +1 @@
{"version":3,"file":"purchase-page-illustration-dark.svg.mjs","sources":["../../../admin/src/assets/purchase-page-illustration-dark.svg"],"sourcesContent":["var img = \"data:image/svg+xml,%3csvg width='1440' height='810' viewBox='0 0 1440 810' fill='none' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'%3e%3cg clip-path='url(%23clip0_347_14228)'%3e%3crect width='1440' height='810' fill='%23181826'/%3e%3cg filter='url(%23filter0_d_347_14228)'%3e%3cg clip-path='url(%23clip1_347_14228)'%3e%3crect x='80' y='80' width='1440' height='900' rx='4' fill='white'/%3e%3cg clip-path='url(%23clip2_347_14228)'%3e%3cmask id='path-4-inside-1_347_14228' fill='white'%3e%3cpath d='M80 80H136V980H80V80Z'/%3e%3c/mask%3e%3cpath d='M80 80H136V980H80V80Z' fill='%23212134'/%3e%3cpath d='M135 80V980H137V80H135Z' fill='%2332324D' mask='url(%23path-4-inside-1_347_14228)'/%3e%3cmask id='path-6-inside-2_347_14228' fill='white'%3e%3cpath d='M80 80H136V136H80V80Z'/%3e%3c/mask%3e%3cpath d='M136 135H80V137H136V135Z' fill='%2332324D' mask='url(%23path-6-inside-2_347_14228)'/%3e%3crect x='96' y='96' width='24' height='24' rx='4' fill='url(%23pattern0_347_14228)'/%3e%3cpath d='M115.5 163.027V170.25C115.5 170.582 115.368 170.899 115.134 171.134C114.899 171.368 114.582 171.5 114.25 171.5H111.125C110.793 171.5 110.476 171.368 110.241 171.134C110.007 170.899 109.875 170.582 109.875 170.25V167.125C109.875 166.959 109.809 166.8 109.692 166.683C109.575 166.566 109.416 166.5 109.25 166.5H106.75C106.584 166.5 106.425 166.566 106.308 166.683C106.191 166.8 106.125 166.959 106.125 167.125V170.25C106.125 170.582 105.993 170.899 105.759 171.134C105.524 171.368 105.207 171.5 104.875 171.5H101.75C101.418 171.5 101.101 171.368 100.866 171.134C100.632 170.899 100.5 170.582 100.5 170.25V163.027C100.5 162.854 100.536 162.683 100.605 162.525C100.675 162.366 100.777 162.224 100.904 162.107L107.154 156.21L107.162 156.202C107.393 155.992 107.692 155.876 108.004 155.876C108.315 155.876 108.614 155.992 108.845 156.202C108.847 156.205 108.85 156.207 108.853 156.21L115.103 162.107C115.229 162.225 115.33 162.367 115.398 162.526C115.466 162.684 115.501 162.855 115.5 163.027Z' fill='%238E8EA9'/%3e%3cpath d='M114.55 208.532L109.879 213.256C109.764 213.372 109.628 213.464 109.478 213.527C109.328 213.589 109.167 213.622 109.004 213.622H103.884L101.567 215.942C101.509 216 101.44 216.046 101.364 216.078C101.288 216.109 101.207 216.125 101.125 216.125C101.043 216.125 100.962 216.109 100.886 216.078C100.81 216.046 100.741 216 100.683 215.942C100.625 215.884 100.579 215.815 100.547 215.739C100.516 215.663 100.5 215.582 100.5 215.5C100.5 215.418 100.516 215.337 100.547 215.261C100.579 215.185 100.625 215.116 100.683 215.058L102.448 213.294L107.742 208H114.328C114.39 208 114.45 208.018 114.501 208.053C114.552 208.087 114.592 208.135 114.616 208.192C114.64 208.249 114.646 208.312 114.634 208.372C114.623 208.433 114.593 208.488 114.55 208.532ZM114.93 200.388C113.971 199.599 112.752 199.195 111.512 199.255C110.271 199.315 109.097 199.834 108.219 200.712L107.469 201.452C107.41 201.51 107.376 201.589 107.375 201.672V206.594L111.672 202.297C111.79 202.185 111.947 202.123 112.11 202.125C112.273 202.127 112.429 202.193 112.544 202.308C112.66 202.423 112.725 202.579 112.727 202.742C112.729 202.905 112.668 203.062 112.555 203.18L108.993 206.75H115.898C115.955 206.75 116.011 206.734 116.06 206.705C116.109 206.675 116.149 206.632 116.176 206.581C116.711 205.563 116.88 204.391 116.653 203.263C116.426 202.135 115.817 201.12 114.93 200.388ZM102.909 211.065L106.125 207.848V203.528C106.125 203.467 106.107 203.406 106.072 203.355C106.038 203.304 105.99 203.264 105.933 203.24C105.876 203.217 105.813 203.21 105.753 203.222C105.692 203.234 105.637 203.263 105.593 203.306L102.741 206.125C102.625 206.241 102.533 206.378 102.47 206.53C102.407 206.682 102.375 206.844 102.375 207.009V210.844C102.375 210.906 102.393 210.966 102.428 211.017C102.462 211.069 102.511 211.109 102.568 211.133C102.625 211.156 102.688 211.163 102.749 211.15C102.809 211.138 102.865 211.109 102.909 211.065Z' fill='%238E8EA9'/%3e%3crect x='92' y='236' width='32' height='32' rx='4' fill='%23181826'/%3e%3cpath d='M116.078 245.464C116.078 245.464 116.078 245.472 116.078 245.476L111.531 260.471C111.462 260.715 111.321 260.931 111.125 261.092C110.93 261.253 110.69 261.35 110.438 261.37C110.402 261.373 110.366 261.375 110.33 261.375C110.093 261.376 109.862 261.309 109.662 261.182C109.462 261.056 109.303 260.874 109.203 260.66L106.413 254.77C106.386 254.712 106.377 254.647 106.388 254.583C106.399 254.52 106.429 254.461 106.475 254.416L110.948 249.941C111.061 249.823 111.122 249.666 111.12 249.503C111.118 249.34 111.053 249.184 110.937 249.069C110.822 248.954 110.666 248.888 110.503 248.886C110.34 248.884 110.183 248.946 110.065 249.058L105.584 253.531C105.539 253.577 105.48 253.607 105.417 253.618C105.353 253.629 105.288 253.621 105.23 253.593L99.3704 250.817C99.1402 250.712 98.9466 250.54 98.8139 250.325C98.6812 25Line truncated
@@ -0,0 +1,6 @@
'use strict';
var img = "data:image/svg+xml,%3csvg width='1440' height='810' viewBox='0 0 1440 810' fill='none' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'%3e%3cg clip-path='url(%23clip0_347_13710)'%3e%3crect width='1440' height='810' fill='%23F0F0FF'/%3e%3cg filter='url(%23filter0_d_347_13710)'%3e%3cg clip-path='url(%23clip1_347_13710)'%3e%3crect x='80' y='80' width='1440' height='900' rx='4' fill='white'/%3e%3cg clip-path='url(%23clip2_347_13710)'%3e%3cmask id='path-4-inside-1_347_13710' fill='white'%3e%3cpath d='M80 80H136V980H80V80Z'/%3e%3c/mask%3e%3cpath d='M80 80H136V980H80V80Z' fill='white'/%3e%3cpath d='M135 80V980H137V80H135Z' fill='%23EAEAEF' mask='url(%23path-4-inside-1_347_13710)'/%3e%3cmask id='path-6-inside-2_347_13710' fill='white'%3e%3cpath d='M80 80H136V136H80V80Z'/%3e%3c/mask%3e%3cpath d='M136 135H80V137H136V135Z' fill='%23EAEAEF' mask='url(%23path-6-inside-2_347_13710)'/%3e%3crect x='96' y='96' width='24' height='24' rx='4' fill='url(%23pattern0_347_13710)'/%3e%3cpath d='M115.5 163.027V170.25C115.5 170.582 115.368 170.899 115.134 171.134C114.899 171.368 114.582 171.5 114.25 171.5H111.125C110.793 171.5 110.476 171.368 110.241 171.134C110.007 170.899 109.875 170.582 109.875 170.25V167.125C109.875 166.959 109.809 166.8 109.692 166.683C109.575 166.566 109.416 166.5 109.25 166.5H106.75C106.584 166.5 106.425 166.566 106.308 166.683C106.191 166.8 106.125 166.959 106.125 167.125V170.25C106.125 170.582 105.993 170.899 105.759 171.134C105.524 171.368 105.207 171.5 104.875 171.5H101.75C101.418 171.5 101.101 171.368 100.866 171.134C100.632 170.899 100.5 170.582 100.5 170.25V163.027C100.5 162.854 100.536 162.683 100.605 162.525C100.675 162.366 100.777 162.224 100.904 162.107L107.154 156.21L107.162 156.202C107.393 155.992 107.692 155.876 108.004 155.876C108.315 155.876 108.614 155.992 108.845 156.202C108.847 156.205 108.85 156.207 108.853 156.21L115.103 162.107C115.229 162.225 115.33 162.367 115.398 162.526C115.466 162.684 115.501 162.855 115.5 163.027Z' fill='%238E8EA9'/%3e%3cpath d='M114.55 208.532L109.879 213.256C109.764 213.372 109.628 213.464 109.478 213.527C109.328 213.589 109.167 213.622 109.004 213.622H103.884L101.567 215.942C101.509 216 101.44 216.046 101.364 216.078C101.288 216.109 101.207 216.125 101.125 216.125C101.043 216.125 100.962 216.109 100.886 216.078C100.81 216.046 100.741 216 100.683 215.942C100.625 215.884 100.579 215.815 100.547 215.739C100.516 215.663 100.5 215.582 100.5 215.5C100.5 215.418 100.516 215.337 100.547 215.261C100.579 215.185 100.625 215.116 100.683 215.058L102.448 213.294L107.742 208H114.328C114.39 208 114.45 208.018 114.501 208.053C114.552 208.087 114.592 208.135 114.616 208.192C114.64 208.249 114.646 208.312 114.634 208.372C114.623 208.433 114.593 208.488 114.55 208.532ZM114.93 200.388C113.971 199.599 112.752 199.195 111.512 199.255C110.271 199.315 109.097 199.834 108.219 200.712L107.469 201.452C107.41 201.51 107.376 201.589 107.375 201.672V206.594L111.672 202.297C111.79 202.185 111.947 202.123 112.11 202.125C112.273 202.127 112.429 202.193 112.544 202.308C112.66 202.423 112.725 202.579 112.727 202.742C112.729 202.905 112.668 203.062 112.555 203.18L108.993 206.75H115.898C115.955 206.75 116.011 206.734 116.06 206.705C116.109 206.675 116.149 206.632 116.176 206.581C116.711 205.563 116.88 204.391 116.653 203.263C116.426 202.135 115.817 201.12 114.93 200.388ZM102.909 211.065L106.125 207.848V203.528C106.125 203.467 106.107 203.406 106.072 203.355C106.038 203.304 105.99 203.264 105.933 203.24C105.876 203.217 105.813 203.21 105.753 203.222C105.692 203.234 105.637 203.263 105.593 203.306L102.741 206.125C102.625 206.241 102.533 206.378 102.47 206.53C102.407 206.682 102.375 206.844 102.375 207.009V210.844C102.375 210.906 102.393 210.966 102.428 211.017C102.462 211.069 102.511 211.109 102.568 211.133C102.625 211.156 102.688 211.163 102.749 211.15C102.809 211.138 102.865 211.109 102.909 211.065Z' fill='%238E8EA9'/%3e%3crect x='92' y='236' width='32' height='32' rx='4' fill='%23F0F0FF'/%3e%3cpath d='M116.078 245.464C116.078 245.464 116.078 245.472 116.078 245.476L111.531 260.471C111.462 260.715 111.321 260.931 111.125 261.092C110.93 261.253 110.69 261.35 110.437 261.37C110.402 261.373 110.366 261.375 110.33 261.375C110.093 261.376 109.862 261.309 109.662 261.182C109.462 261.056 109.303 260.874 109.203 260.66L106.413 254.77C106.386 254.712 106.377 254.647 106.388 254.583C106.399 254.52 106.429 254.461 106.475 254.416L110.948 249.941C111.061 249.823 111.122 249.666 111.12 249.503C111.118 249.34 111.053 249.184 110.937 249.069C110.822 248.954 110.666 248.888 110.503 248.886C110.34 248.884 110.183 248.946 110.065 249.058L105.584 253.531C105.539 253.577 105.48 253.607 105.417 253.618C105.353 253.629 105.288 253.621 105.23 253.593L99.3703 250.817C99.1402 250.712 98.9465 250.54 98.8138 250.325C98.6812 250.109 98.6154 249.859 98.625 249.606C98.6377 249.346 98.7318 249.097 98.894 248.893C99.0562 248.69 99.2783 248.542 99.5289 248.472L114.524 243.925H114.536C114.749Line truncated
module.exports = img;
//# sourceMappingURL=purchase-page-illustration-light.svg.js.map
@@ -0,0 +1 @@
{"version":3,"file":"purchase-page-illustration-light.svg.js","sources":["../../../admin/src/assets/purchase-page-illustration-light.svg"],"sourcesContent":["var img = \"data:image/svg+xml,%3csvg width='1440' height='810' viewBox='0 0 1440 810' fill='none' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'%3e%3cg clip-path='url(%23clip0_347_13710)'%3e%3crect width='1440' height='810' fill='%23F0F0FF'/%3e%3cg filter='url(%23filter0_d_347_13710)'%3e%3cg clip-path='url(%23clip1_347_13710)'%3e%3crect x='80' y='80' width='1440' height='900' rx='4' fill='white'/%3e%3cg clip-path='url(%23clip2_347_13710)'%3e%3cmask id='path-4-inside-1_347_13710' fill='white'%3e%3cpath d='M80 80H136V980H80V80Z'/%3e%3c/mask%3e%3cpath d='M80 80H136V980H80V80Z' fill='white'/%3e%3cpath d='M135 80V980H137V80H135Z' fill='%23EAEAEF' mask='url(%23path-4-inside-1_347_13710)'/%3e%3cmask id='path-6-inside-2_347_13710' fill='white'%3e%3cpath d='M80 80H136V136H80V80Z'/%3e%3c/mask%3e%3cpath d='M136 135H80V137H136V135Z' fill='%23EAEAEF' mask='url(%23path-6-inside-2_347_13710)'/%3e%3crect x='96' y='96' width='24' height='24' rx='4' fill='url(%23pattern0_347_13710)'/%3e%3cpath d='M115.5 163.027V170.25C115.5 170.582 115.368 170.899 115.134 171.134C114.899 171.368 114.582 171.5 114.25 171.5H111.125C110.793 171.5 110.476 171.368 110.241 171.134C110.007 170.899 109.875 170.582 109.875 170.25V167.125C109.875 166.959 109.809 166.8 109.692 166.683C109.575 166.566 109.416 166.5 109.25 166.5H106.75C106.584 166.5 106.425 166.566 106.308 166.683C106.191 166.8 106.125 166.959 106.125 167.125V170.25C106.125 170.582 105.993 170.899 105.759 171.134C105.524 171.368 105.207 171.5 104.875 171.5H101.75C101.418 171.5 101.101 171.368 100.866 171.134C100.632 170.899 100.5 170.582 100.5 170.25V163.027C100.5 162.854 100.536 162.683 100.605 162.525C100.675 162.366 100.777 162.224 100.904 162.107L107.154 156.21L107.162 156.202C107.393 155.992 107.692 155.876 108.004 155.876C108.315 155.876 108.614 155.992 108.845 156.202C108.847 156.205 108.85 156.207 108.853 156.21L115.103 162.107C115.229 162.225 115.33 162.367 115.398 162.526C115.466 162.684 115.501 162.855 115.5 163.027Z' fill='%238E8EA9'/%3e%3cpath d='M114.55 208.532L109.879 213.256C109.764 213.372 109.628 213.464 109.478 213.527C109.328 213.589 109.167 213.622 109.004 213.622H103.884L101.567 215.942C101.509 216 101.44 216.046 101.364 216.078C101.288 216.109 101.207 216.125 101.125 216.125C101.043 216.125 100.962 216.109 100.886 216.078C100.81 216.046 100.741 216 100.683 215.942C100.625 215.884 100.579 215.815 100.547 215.739C100.516 215.663 100.5 215.582 100.5 215.5C100.5 215.418 100.516 215.337 100.547 215.261C100.579 215.185 100.625 215.116 100.683 215.058L102.448 213.294L107.742 208H114.328C114.39 208 114.45 208.018 114.501 208.053C114.552 208.087 114.592 208.135 114.616 208.192C114.64 208.249 114.646 208.312 114.634 208.372C114.623 208.433 114.593 208.488 114.55 208.532ZM114.93 200.388C113.971 199.599 112.752 199.195 111.512 199.255C110.271 199.315 109.097 199.834 108.219 200.712L107.469 201.452C107.41 201.51 107.376 201.589 107.375 201.672V206.594L111.672 202.297C111.79 202.185 111.947 202.123 112.11 202.125C112.273 202.127 112.429 202.193 112.544 202.308C112.66 202.423 112.725 202.579 112.727 202.742C112.729 202.905 112.668 203.062 112.555 203.18L108.993 206.75H115.898C115.955 206.75 116.011 206.734 116.06 206.705C116.109 206.675 116.149 206.632 116.176 206.581C116.711 205.563 116.88 204.391 116.653 203.263C116.426 202.135 115.817 201.12 114.93 200.388ZM102.909 211.065L106.125 207.848V203.528C106.125 203.467 106.107 203.406 106.072 203.355C106.038 203.304 105.99 203.264 105.933 203.24C105.876 203.217 105.813 203.21 105.753 203.222C105.692 203.234 105.637 203.263 105.593 203.306L102.741 206.125C102.625 206.241 102.533 206.378 102.47 206.53C102.407 206.682 102.375 206.844 102.375 207.009V210.844C102.375 210.906 102.393 210.966 102.428 211.017C102.462 211.069 102.511 211.109 102.568 211.133C102.625 211.156 102.688 211.163 102.749 211.15C102.809 211.138 102.865 211.109 102.909 211.065Z' fill='%238E8EA9'/%3e%3crect x='92' y='236' width='32' height='32' rx='4' fill='%23F0F0FF'/%3e%3cpath d='M116.078 245.464C116.078 245.464 116.078 245.472 116.078 245.476L111.531 260.471C111.462 260.715 111.321 260.931 111.125 261.092C110.93 261.253 110.69 261.35 110.437 261.37C110.402 261.373 110.366 261.375 110.33 261.375C110.093 261.376 109.862 261.309 109.662 261.182C109.462 261.056 109.303 260.874 109.203 260.66L106.413 254.77C106.386 254.712 106.377 254.647 106.388 254.583C106.399 254.52 106.429 254.461 106.475 254.416L110.948 249.941C111.061 249.823 111.122 249.666 111.12 249.503C111.118 249.34 111.053 249.184 110.937 249.069C110.822 248.954 110.666 248.888 110.503 248.886C110.34 248.884 110.183 248.946 110.065 249.058L105.584 253.531C105.539 253.577 105.48 253.607 105.417 253.618C105.353 253.629 105.288 253.621 105.23 253.593L99.3703 250.817C99.1402 250.712 98.9465 250.54 98.8138 250.325C98.6812 250.1Line truncated
@@ -0,0 +1,4 @@
var img = "data:image/svg+xml,%3csvg width='1440' height='810' viewBox='0 0 1440 810' fill='none' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'%3e%3cg clip-path='url(%23clip0_347_13710)'%3e%3crect width='1440' height='810' fill='%23F0F0FF'/%3e%3cg filter='url(%23filter0_d_347_13710)'%3e%3cg clip-path='url(%23clip1_347_13710)'%3e%3crect x='80' y='80' width='1440' height='900' rx='4' fill='white'/%3e%3cg clip-path='url(%23clip2_347_13710)'%3e%3cmask id='path-4-inside-1_347_13710' fill='white'%3e%3cpath d='M80 80H136V980H80V80Z'/%3e%3c/mask%3e%3cpath d='M80 80H136V980H80V80Z' fill='white'/%3e%3cpath d='M135 80V980H137V80H135Z' fill='%23EAEAEF' mask='url(%23path-4-inside-1_347_13710)'/%3e%3cmask id='path-6-inside-2_347_13710' fill='white'%3e%3cpath d='M80 80H136V136H80V80Z'/%3e%3c/mask%3e%3cpath d='M136 135H80V137H136V135Z' fill='%23EAEAEF' mask='url(%23path-6-inside-2_347_13710)'/%3e%3crect x='96' y='96' width='24' height='24' rx='4' fill='url(%23pattern0_347_13710)'/%3e%3cpath d='M115.5 163.027V170.25C115.5 170.582 115.368 170.899 115.134 171.134C114.899 171.368 114.582 171.5 114.25 171.5H111.125C110.793 171.5 110.476 171.368 110.241 171.134C110.007 170.899 109.875 170.582 109.875 170.25V167.125C109.875 166.959 109.809 166.8 109.692 166.683C109.575 166.566 109.416 166.5 109.25 166.5H106.75C106.584 166.5 106.425 166.566 106.308 166.683C106.191 166.8 106.125 166.959 106.125 167.125V170.25C106.125 170.582 105.993 170.899 105.759 171.134C105.524 171.368 105.207 171.5 104.875 171.5H101.75C101.418 171.5 101.101 171.368 100.866 171.134C100.632 170.899 100.5 170.582 100.5 170.25V163.027C100.5 162.854 100.536 162.683 100.605 162.525C100.675 162.366 100.777 162.224 100.904 162.107L107.154 156.21L107.162 156.202C107.393 155.992 107.692 155.876 108.004 155.876C108.315 155.876 108.614 155.992 108.845 156.202C108.847 156.205 108.85 156.207 108.853 156.21L115.103 162.107C115.229 162.225 115.33 162.367 115.398 162.526C115.466 162.684 115.501 162.855 115.5 163.027Z' fill='%238E8EA9'/%3e%3cpath d='M114.55 208.532L109.879 213.256C109.764 213.372 109.628 213.464 109.478 213.527C109.328 213.589 109.167 213.622 109.004 213.622H103.884L101.567 215.942C101.509 216 101.44 216.046 101.364 216.078C101.288 216.109 101.207 216.125 101.125 216.125C101.043 216.125 100.962 216.109 100.886 216.078C100.81 216.046 100.741 216 100.683 215.942C100.625 215.884 100.579 215.815 100.547 215.739C100.516 215.663 100.5 215.582 100.5 215.5C100.5 215.418 100.516 215.337 100.547 215.261C100.579 215.185 100.625 215.116 100.683 215.058L102.448 213.294L107.742 208H114.328C114.39 208 114.45 208.018 114.501 208.053C114.552 208.087 114.592 208.135 114.616 208.192C114.64 208.249 114.646 208.312 114.634 208.372C114.623 208.433 114.593 208.488 114.55 208.532ZM114.93 200.388C113.971 199.599 112.752 199.195 111.512 199.255C110.271 199.315 109.097 199.834 108.219 200.712L107.469 201.452C107.41 201.51 107.376 201.589 107.375 201.672V206.594L111.672 202.297C111.79 202.185 111.947 202.123 112.11 202.125C112.273 202.127 112.429 202.193 112.544 202.308C112.66 202.423 112.725 202.579 112.727 202.742C112.729 202.905 112.668 203.062 112.555 203.18L108.993 206.75H115.898C115.955 206.75 116.011 206.734 116.06 206.705C116.109 206.675 116.149 206.632 116.176 206.581C116.711 205.563 116.88 204.391 116.653 203.263C116.426 202.135 115.817 201.12 114.93 200.388ZM102.909 211.065L106.125 207.848V203.528C106.125 203.467 106.107 203.406 106.072 203.355C106.038 203.304 105.99 203.264 105.933 203.24C105.876 203.217 105.813 203.21 105.753 203.222C105.692 203.234 105.637 203.263 105.593 203.306L102.741 206.125C102.625 206.241 102.533 206.378 102.47 206.53C102.407 206.682 102.375 206.844 102.375 207.009V210.844C102.375 210.906 102.393 210.966 102.428 211.017C102.462 211.069 102.511 211.109 102.568 211.133C102.625 211.156 102.688 211.163 102.749 211.15C102.809 211.138 102.865 211.109 102.909 211.065Z' fill='%238E8EA9'/%3e%3crect x='92' y='236' width='32' height='32' rx='4' fill='%23F0F0FF'/%3e%3cpath d='M116.078 245.464C116.078 245.464 116.078 245.472 116.078 245.476L111.531 260.471C111.462 260.715 111.321 260.931 111.125 261.092C110.93 261.253 110.69 261.35 110.437 261.37C110.402 261.373 110.366 261.375 110.33 261.375C110.093 261.376 109.862 261.309 109.662 261.182C109.462 261.056 109.303 260.874 109.203 260.66L106.413 254.77C106.386 254.712 106.377 254.647 106.388 254.583C106.399 254.52 106.429 254.461 106.475 254.416L110.948 249.941C111.061 249.823 111.122 249.666 111.12 249.503C111.118 249.34 111.053 249.184 110.937 249.069C110.822 248.954 110.666 248.888 110.503 248.886C110.34 248.884 110.183 248.946 110.065 249.058L105.584 253.531C105.539 253.577 105.48 253.607 105.417 253.618C105.353 253.629 105.288 253.621 105.23 253.593L99.3703 250.817C99.1402 250.712 98.9465 250.54 98.8138 250.325C98.6812 250.109 98.6154 249.859 98.625 249.606C98.6377 249.346 98.7318 249.097 98.894 248.893C99.0562 248.69 99.2783 248.542 99.5289 248.472L114.524 243.925H114.536C114.749Line truncated
export { img as default };
//# sourceMappingURL=purchase-page-illustration-light.svg.mjs.map
@@ -0,0 +1 @@
{"version":3,"file":"purchase-page-illustration-light.svg.mjs","sources":["../../../admin/src/assets/purchase-page-illustration-light.svg"],"sourcesContent":["var img = \"data:image/svg+xml,%3csvg width='1440' height='810' viewBox='0 0 1440 810' fill='none' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'%3e%3cg clip-path='url(%23clip0_347_13710)'%3e%3crect width='1440' height='810' fill='%23F0F0FF'/%3e%3cg filter='url(%23filter0_d_347_13710)'%3e%3cg clip-path='url(%23clip1_347_13710)'%3e%3crect x='80' y='80' width='1440' height='900' rx='4' fill='white'/%3e%3cg clip-path='url(%23clip2_347_13710)'%3e%3cmask id='path-4-inside-1_347_13710' fill='white'%3e%3cpath d='M80 80H136V980H80V80Z'/%3e%3c/mask%3e%3cpath d='M80 80H136V980H80V80Z' fill='white'/%3e%3cpath d='M135 80V980H137V80H135Z' fill='%23EAEAEF' mask='url(%23path-4-inside-1_347_13710)'/%3e%3cmask id='path-6-inside-2_347_13710' fill='white'%3e%3cpath d='M80 80H136V136H80V80Z'/%3e%3c/mask%3e%3cpath d='M136 135H80V137H136V135Z' fill='%23EAEAEF' mask='url(%23path-6-inside-2_347_13710)'/%3e%3crect x='96' y='96' width='24' height='24' rx='4' fill='url(%23pattern0_347_13710)'/%3e%3cpath d='M115.5 163.027V170.25C115.5 170.582 115.368 170.899 115.134 171.134C114.899 171.368 114.582 171.5 114.25 171.5H111.125C110.793 171.5 110.476 171.368 110.241 171.134C110.007 170.899 109.875 170.582 109.875 170.25V167.125C109.875 166.959 109.809 166.8 109.692 166.683C109.575 166.566 109.416 166.5 109.25 166.5H106.75C106.584 166.5 106.425 166.566 106.308 166.683C106.191 166.8 106.125 166.959 106.125 167.125V170.25C106.125 170.582 105.993 170.899 105.759 171.134C105.524 171.368 105.207 171.5 104.875 171.5H101.75C101.418 171.5 101.101 171.368 100.866 171.134C100.632 170.899 100.5 170.582 100.5 170.25V163.027C100.5 162.854 100.536 162.683 100.605 162.525C100.675 162.366 100.777 162.224 100.904 162.107L107.154 156.21L107.162 156.202C107.393 155.992 107.692 155.876 108.004 155.876C108.315 155.876 108.614 155.992 108.845 156.202C108.847 156.205 108.85 156.207 108.853 156.21L115.103 162.107C115.229 162.225 115.33 162.367 115.398 162.526C115.466 162.684 115.501 162.855 115.5 163.027Z' fill='%238E8EA9'/%3e%3cpath d='M114.55 208.532L109.879 213.256C109.764 213.372 109.628 213.464 109.478 213.527C109.328 213.589 109.167 213.622 109.004 213.622H103.884L101.567 215.942C101.509 216 101.44 216.046 101.364 216.078C101.288 216.109 101.207 216.125 101.125 216.125C101.043 216.125 100.962 216.109 100.886 216.078C100.81 216.046 100.741 216 100.683 215.942C100.625 215.884 100.579 215.815 100.547 215.739C100.516 215.663 100.5 215.582 100.5 215.5C100.5 215.418 100.516 215.337 100.547 215.261C100.579 215.185 100.625 215.116 100.683 215.058L102.448 213.294L107.742 208H114.328C114.39 208 114.45 208.018 114.501 208.053C114.552 208.087 114.592 208.135 114.616 208.192C114.64 208.249 114.646 208.312 114.634 208.372C114.623 208.433 114.593 208.488 114.55 208.532ZM114.93 200.388C113.971 199.599 112.752 199.195 111.512 199.255C110.271 199.315 109.097 199.834 108.219 200.712L107.469 201.452C107.41 201.51 107.376 201.589 107.375 201.672V206.594L111.672 202.297C111.79 202.185 111.947 202.123 112.11 202.125C112.273 202.127 112.429 202.193 112.544 202.308C112.66 202.423 112.725 202.579 112.727 202.742C112.729 202.905 112.668 203.062 112.555 203.18L108.993 206.75H115.898C115.955 206.75 116.011 206.734 116.06 206.705C116.109 206.675 116.149 206.632 116.176 206.581C116.711 205.563 116.88 204.391 116.653 203.263C116.426 202.135 115.817 201.12 114.93 200.388ZM102.909 211.065L106.125 207.848V203.528C106.125 203.467 106.107 203.406 106.072 203.355C106.038 203.304 105.99 203.264 105.933 203.24C105.876 203.217 105.813 203.21 105.753 203.222C105.692 203.234 105.637 203.263 105.593 203.306L102.741 206.125C102.625 206.241 102.533 206.378 102.47 206.53C102.407 206.682 102.375 206.844 102.375 207.009V210.844C102.375 210.906 102.393 210.966 102.428 211.017C102.462 211.069 102.511 211.109 102.568 211.133C102.625 211.156 102.688 211.163 102.749 211.15C102.809 211.138 102.865 211.109 102.909 211.065Z' fill='%238E8EA9'/%3e%3crect x='92' y='236' width='32' height='32' rx='4' fill='%23F0F0FF'/%3e%3cpath d='M116.078 245.464C116.078 245.464 116.078 245.472 116.078 245.476L111.531 260.471C111.462 260.715 111.321 260.931 111.125 261.092C110.93 261.253 110.69 261.35 110.437 261.37C110.402 261.373 110.366 261.375 110.33 261.375C110.093 261.376 109.862 261.309 109.662 261.182C109.462 261.056 109.303 260.874 109.203 260.66L106.413 254.77C106.386 254.712 106.377 254.647 106.388 254.583C106.399 254.52 106.429 254.461 106.475 254.416L110.948 249.941C111.061 249.823 111.122 249.666 111.12 249.503C111.118 249.34 111.053 249.184 110.937 249.069C110.822 248.954 110.666 248.888 110.503 248.886C110.34 248.884 110.183 248.946 110.065 249.058L105.584 253.531C105.539 253.577 105.48 253.607 105.417 253.618C105.353 253.629 105.288 253.621 105.23 253.593L99.3703 250.817C99.1402 250.712 98.9465 250.54 98.8138 250.325C98.6812 250.Line truncated
@@ -0,0 +1,344 @@
'use strict';
var jsxRuntime = require('react/jsx-runtime');
require('react');
var strapiAdmin = require('@strapi/content-manager/strapi-admin');
var designSystem = require('@strapi/design-system');
var icons = require('@strapi/icons');
var qs = require('qs');
var reactIntl = require('react-intl');
var reactRouterDom = require('react-router-dom');
var styledComponents = require('styled-components');
const StyledPopoverFlex = styledComponents.styled(designSystem.Flex)`
width: 100%;
max-width: 256px;
& > * {
border-bottom: 1px solid ${({ theme })=>theme.colors.neutral150};
}
& > *:last-child {
border-bottom: none;
}
`;
const EntryStatusTrigger = ({ action, status, hasErrors, requiredStage, entryStage })=>{
const { formatMessage } = reactIntl.useIntl();
if (action === 'publish') {
if (hasErrors || requiredStage && requiredStage.id !== entryStage?.id) {
return /*#__PURE__*/ jsxRuntime.jsx(designSystem.Popover.Trigger, {
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Button, {
variant: "ghost",
startIcon: /*#__PURE__*/ jsxRuntime.jsx(icons.CrossCircle, {
fill: "danger600"
}),
endIcon: /*#__PURE__*/ jsxRuntime.jsx(icons.CaretDown, {}),
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
textColor: "danger600",
variant: "omega",
fontWeight: "bold",
children: formatMessage({
id: 'content-releases.pages.ReleaseDetails.entry-validation.not-ready',
defaultMessage: 'Not ready to publish'
})
})
})
});
}
if (status === 'draft') {
return /*#__PURE__*/ jsxRuntime.jsx(designSystem.Popover.Trigger, {
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Button, {
variant: "ghost",
startIcon: /*#__PURE__*/ jsxRuntime.jsx(icons.CheckCircle, {
fill: "success600"
}),
endIcon: /*#__PURE__*/ jsxRuntime.jsx(icons.CaretDown, {}),
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
textColor: "success600",
variant: "omega",
fontWeight: "bold",
children: formatMessage({
id: 'content-releases.pages.ReleaseDetails.entry-validation.ready-to-publish',
defaultMessage: 'Ready to publish'
})
})
})
});
}
if (status === 'modified') {
return /*#__PURE__*/ jsxRuntime.jsx(designSystem.Popover.Trigger, {
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Button, {
variant: "ghost",
startIcon: /*#__PURE__*/ jsxRuntime.jsx(icons.ArrowsCounterClockwise, {
fill: "alternative600"
}),
endIcon: /*#__PURE__*/ jsxRuntime.jsx(icons.CaretDown, {}),
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
variant: "omega",
fontWeight: "bold",
textColor: "alternative600",
children: formatMessage({
id: 'content-releases.pages.ReleaseDetails.entry-validation.modified',
defaultMessage: 'Ready to publish changes'
})
})
})
});
}
return /*#__PURE__*/ jsxRuntime.jsx(designSystem.Popover.Trigger, {
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Button, {
variant: "ghost",
startIcon: /*#__PURE__*/ jsxRuntime.jsx(icons.CheckCircle, {
fill: "success600"
}),
endIcon: /*#__PURE__*/ jsxRuntime.jsx(icons.CaretDown, {}),
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
textColor: "success600",
variant: "omega",
fontWeight: "bold",
children: formatMessage({
id: 'content-releases.pages.ReleaseDetails.entry-validation.already-published',
defaultMessage: 'Already published'
})
})
})
});
}
if (status === 'published') {
return /*#__PURE__*/ jsxRuntime.jsx(designSystem.Popover.Trigger, {
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Button, {
variant: "ghost",
startIcon: /*#__PURE__*/ jsxRuntime.jsx(icons.CheckCircle, {
fill: "success600"
}),
endIcon: /*#__PURE__*/ jsxRuntime.jsx(icons.CaretDown, {}),
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
textColor: "success600",
variant: "omega",
fontWeight: "bold",
children: formatMessage({
id: 'content-releases.pages.ReleaseDetails.entry-validation.ready-to-unpublish',
defaultMessage: 'Ready to unpublish'
})
})
})
});
}
return /*#__PURE__*/ jsxRuntime.jsx(designSystem.Popover.Trigger, {
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Button, {
variant: "ghost",
startIcon: /*#__PURE__*/ jsxRuntime.jsx(icons.CheckCircle, {
fill: "success600"
}),
endIcon: /*#__PURE__*/ jsxRuntime.jsx(icons.CaretDown, {}),
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
textColor: "success600",
variant: "omega",
fontWeight: "bold",
children: formatMessage({
id: 'content-releases.pages.ReleaseDetails.entry-validation.already-unpublished',
defaultMessage: 'Already unpublished'
})
})
})
});
};
const FieldsValidation = ({ hasErrors, errors, kind, contentTypeUid, documentId, locale })=>{
const { formatMessage } = reactIntl.useIntl();
return /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
direction: "column",
gap: 1,
width: "100%",
padding: 5,
children: [
/*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
gap: 2,
width: "100%",
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
fontWeight: "bold",
children: formatMessage({
id: 'content-releases.pages.ReleaseDetails.entry-validation.fields',
defaultMessage: 'Fields'
})
}),
hasErrors ? /*#__PURE__*/ jsxRuntime.jsx(icons.CrossCircle, {
fill: "danger600"
}) : /*#__PURE__*/ jsxRuntime.jsx(icons.CheckCircle, {
fill: "success600"
})
]
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
width: "100%",
textColor: "neutral600",
children: hasErrors ? formatMessage({
id: 'content-releases.pages.ReleaseDetails.entry-validation.fields.error',
defaultMessage: '{errors} errors on fields.'
}, {
errors: errors ? Object.keys(errors).length : 0
}) : formatMessage({
id: 'content-releases.pages.ReleaseDetails.entry-validation.fields.success',
defaultMessage: 'All fields are filled correctly.'
})
}),
hasErrors && /*#__PURE__*/ jsxRuntime.jsx(designSystem.LinkButton, {
tag: reactRouterDom.Link,
to: {
pathname: `/content-manager/${kind === 'collectionType' ? 'collection-types' : 'single-types'}/${contentTypeUid}/${documentId}`,
search: locale ? qs.stringify({
plugins: {
i18n: {
locale
}
}
}) : ''
},
variant: "secondary",
fullWidth: true,
state: {
forceValidation: true
},
children: formatMessage({
id: 'content-releases.pages.ReleaseDetails.entry-validation.fields.see-errors',
defaultMessage: 'See errors'
})
})
]
});
};
const getReviewStageIcon = ({ contentTypeHasReviewWorkflow, requiredStage, entryStage })=>{
if (!contentTypeHasReviewWorkflow) {
return /*#__PURE__*/ jsxRuntime.jsx(icons.CheckCircle, {
fill: "neutral200"
});
}
if (requiredStage && requiredStage.id !== entryStage?.id) {
return /*#__PURE__*/ jsxRuntime.jsx(icons.CrossCircle, {
fill: "danger600"
});
}
return /*#__PURE__*/ jsxRuntime.jsx(icons.CheckCircle, {
fill: "success600"
});
};
const getReviewStageMessage = ({ contentTypeHasReviewWorkflow, requiredStage, entryStage, formatMessage })=>{
if (!contentTypeHasReviewWorkflow) {
return formatMessage({
id: 'content-releases.pages.ReleaseDetails.entry-validation.review-stage.not-enabled',
defaultMessage: 'This entry is not associated to any workflow.'
});
}
if (requiredStage && requiredStage.id !== entryStage?.id) {
return formatMessage({
id: 'content-releases.pages.ReleaseDetails.entry-validation.review-stage.not-ready',
defaultMessage: 'This entry is not at the required stage for publishing. ({stageName})'
}, {
stageName: requiredStage?.name ?? ''
});
}
if (requiredStage && requiredStage.id === entryStage?.id) {
return formatMessage({
id: 'content-releases.pages.ReleaseDetails.entry-validation.review-stage.ready',
defaultMessage: 'This entry is at the required stage for publishing. ({stageName})'
}, {
stageName: requiredStage?.name ?? ''
});
}
return formatMessage({
id: 'content-releases.pages.ReleaseDetails.entry-validation.review-stage.stage-not-required',
defaultMessage: 'No required stage for publication'
});
};
const ReviewStageValidation = ({ contentTypeHasReviewWorkflow, requiredStage, entryStage })=>{
const { formatMessage } = reactIntl.useIntl();
const Icon = getReviewStageIcon({
contentTypeHasReviewWorkflow,
requiredStage,
entryStage
});
return /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
direction: "column",
gap: 1,
width: "100%",
padding: 5,
children: [
/*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
gap: 2,
width: "100%",
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
fontWeight: "bold",
children: formatMessage({
id: 'content-releases.pages.ReleaseDetails.entry-validation.review-stage',
defaultMessage: 'Review stage'
})
}),
Icon
]
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
textColor: "neutral600",
children: getReviewStageMessage({
contentTypeHasReviewWorkflow,
requiredStage,
entryStage,
formatMessage
})
})
]
});
};
const EntryValidationPopover = ({ schema, entry, status, action })=>{
const { validate, isLoading } = strapiAdmin.unstable_useDocument({
collectionType: schema?.kind ?? '',
model: schema?.uid ?? ''
}, {
// useDocument makes a request to get more data about the entry, but we only want to have the validation function so we skip the request
skip: true
});
// Validation errors
const errors = isLoading ? null : validate(entry);
const hasErrors = errors ? Object.keys(errors).length > 0 : false;
// Entry stage
const contentTypeHasReviewWorkflow = schema?.hasReviewWorkflow ?? false;
const requiredStage = schema?.stageRequiredToPublish;
const entryStage = entry.strapi_stage;
if (isLoading) {
return null;
}
return /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Popover.Root, {
children: [
/*#__PURE__*/ jsxRuntime.jsx(EntryStatusTrigger, {
action: action,
status: status,
hasErrors: hasErrors,
requiredStage: requiredStage,
entryStage: entryStage
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Popover.Content, {
children: /*#__PURE__*/ jsxRuntime.jsxs(StyledPopoverFlex, {
direction: "column",
children: [
/*#__PURE__*/ jsxRuntime.jsx(FieldsValidation, {
hasErrors: hasErrors,
errors: errors,
contentTypeUid: schema?.uid,
kind: schema?.kind,
documentId: entry.documentId,
locale: entry.locale
}),
/*#__PURE__*/ jsxRuntime.jsx(ReviewStageValidation, {
contentTypeHasReviewWorkflow: contentTypeHasReviewWorkflow,
requiredStage: requiredStage,
entryStage: entryStage
})
]
})
})
]
});
};
exports.EntryValidationPopover = EntryValidationPopover;
//# sourceMappingURL=EntryValidationPopover.js.map
@@ -0,0 +1 @@
{"version":3,"file":"EntryValidationPopover.js","sources":["../../../admin/src/components/EntryValidationPopover.tsx"],"sourcesContent":["import * as React from 'react';\n\nimport { FormErrors, FormValues } from '@strapi/admin/strapi-admin';\nimport { unstable_useDocument } from '@strapi/content-manager/strapi-admin';\nimport { Button, LinkButton, Flex, Typography, Popover } from '@strapi/design-system';\nimport { CheckCircle, CrossCircle, ArrowsCounterClockwise, CaretDown } from '@strapi/icons';\nimport { stringify } from 'qs';\nimport { useIntl, MessageDescriptor } from 'react-intl';\nimport { Link } from 'react-router-dom';\nimport { styled } from 'styled-components';\n\nimport type {\n ReleaseAction,\n ReleaseActionEntry,\n Stage,\n} from '../../../shared/contracts/release-actions';\nimport type { Struct } from '@strapi/types';\n\nconst StyledPopoverFlex = styled(Flex)`\n width: 100%;\n max-width: 256px;\n\n & > * {\n border-bottom: 1px solid ${({ theme }) => theme.colors.neutral150};\n }\n\n & > *:last-child {\n border-bottom: none;\n }\n`;\n\ninterface EntryValidationPopoverProps {\n action: ReleaseAction['type'];\n schema?: Struct.ContentTypeSchema & {\n hasReviewWorkflow: boolean;\n stageRequiredToPublish?: Stage;\n };\n entry: ReleaseActionEntry;\n status: ReleaseAction['status'];\n}\n\ninterface ValidationStatusProps {\n action: ReleaseAction['type'];\n status: ReleaseAction['status'];\n hasErrors: boolean | null;\n requiredStage?: Stage;\n entryStage?: Stage;\n}\n\nconst EntryStatusTrigger = ({\n action,\n status,\n hasErrors,\n requiredStage,\n entryStage,\n}: ValidationStatusProps) => {\n const { formatMessage } = useIntl();\n\n if (action === 'publish') {\n if (hasErrors || (requiredStage && requiredStage.id !== entryStage?.id)) {\n return (\n <Popover.Trigger>\n <Button\n variant=\"ghost\"\n startIcon={<CrossCircle fill=\"danger600\" />}\n endIcon={<CaretDown />}\n >\n <Typography textColor=\"danger600\" variant=\"omega\" fontWeight=\"bold\">\n {formatMessage({\n id: 'content-releases.pages.ReleaseDetails.entry-validation.not-ready',\n defaultMessage: 'Not ready to publish',\n })}\n </Typography>\n </Button>\n </Popover.Trigger>\n );\n }\n\n if (status === 'draft') {\n return (\n <Popover.Trigger>\n <Button\n variant=\"ghost\"\n startIcon={<CheckCircle fill=\"success600\" />}\n endIcon={<CaretDown />}\n >\n <Typography textColor=\"success600\" variant=\"omega\" fontWeight=\"bold\">\n {formatMessage({\n id: 'content-releases.pages.ReleaseDetails.entry-validation.ready-to-publish',\n defaultMessage: 'Ready to publish',\n })}\n </Typography>\n </Button>\n </Popover.Trigger>\n );\n }\n\n if (status === 'modified') {\n return (\n <Popover.Trigger>\n <Button\n variant=\"ghost\"\n startIcon={<ArrowsCounterClockwise fill=\"alternative600\" />}\n endIcon={<CaretDown />}\n >\n <Typography variant=\"omega\" fontWeight=\"bold\" textColor=\"alternative600\">\n {formatMessage({\n id: 'content-releases.pages.ReleaseDetails.entry-validation.modified',\n defaultMessage: 'Ready to publish changes',\n })}\n </Typography>\n </Button>\n </Popover.Trigger>\n );\n }\n\n return (\n <Popover.Trigger>\n <Button\n variant=\"ghost\"\n startIcon={<CheckCircle fill=\"success600\" />}\n endIcon={<CaretDown />}\n >\n <Typography textColor=\"success600\" variant=\"omega\" fontWeight=\"bold\">\n {formatMessage({\n id: 'content-releases.pages.ReleaseDetails.entry-validation.already-published',\n defaultMessage: 'Already published',\n })}\n </Typography>\n </Button>\n </Popover.Trigger>\n );\n }\n\n if (status === 'published') {\n return (\n <Popover.Trigger>\n <Button\n variant=\"ghost\"\n startIcon={<CheckCircle fill=\"success600\" />}\n endIcon={<CaretDown />}\n >\n <Typography textColor=\"success600\" variant=\"omega\" fontWeight=\"bold\">\n {formatMessage({\n id: 'content-releases.pages.ReleaseDetails.entry-validation.ready-to-unpublish',\n defaultMessage: 'Ready to unpublish',\n })}\n </Typography>\n </Button>\n </Popover.Trigger>\n );\n }\n\n return (\n <Popover.Trigger>\n <Button variant=\"ghost\" startIcon={<CheckCircle fill=\"success600\" />} endIcon={<CaretDown />}>\n <Typography textColor=\"success600\" varianLine truncated
@@ -0,0 +1,342 @@
import { jsxs, jsx } from 'react/jsx-runtime';
import 'react';
import { unstable_useDocument } from '@strapi/content-manager/strapi-admin';
import { Flex, Popover, Button, Typography, LinkButton } from '@strapi/design-system';
import { CrossCircle, CaretDown, CheckCircle, ArrowsCounterClockwise } from '@strapi/icons';
import { stringify } from 'qs';
import { useIntl } from 'react-intl';
import { Link } from 'react-router-dom';
import { styled } from 'styled-components';
const StyledPopoverFlex = styled(Flex)`
width: 100%;
max-width: 256px;
& > * {
border-bottom: 1px solid ${({ theme })=>theme.colors.neutral150};
}
& > *:last-child {
border-bottom: none;
}
`;
const EntryStatusTrigger = ({ action, status, hasErrors, requiredStage, entryStage })=>{
const { formatMessage } = useIntl();
if (action === 'publish') {
if (hasErrors || requiredStage && requiredStage.id !== entryStage?.id) {
return /*#__PURE__*/ jsx(Popover.Trigger, {
children: /*#__PURE__*/ jsx(Button, {
variant: "ghost",
startIcon: /*#__PURE__*/ jsx(CrossCircle, {
fill: "danger600"
}),
endIcon: /*#__PURE__*/ jsx(CaretDown, {}),
children: /*#__PURE__*/ jsx(Typography, {
textColor: "danger600",
variant: "omega",
fontWeight: "bold",
children: formatMessage({
id: 'content-releases.pages.ReleaseDetails.entry-validation.not-ready',
defaultMessage: 'Not ready to publish'
})
})
})
});
}
if (status === 'draft') {
return /*#__PURE__*/ jsx(Popover.Trigger, {
children: /*#__PURE__*/ jsx(Button, {
variant: "ghost",
startIcon: /*#__PURE__*/ jsx(CheckCircle, {
fill: "success600"
}),
endIcon: /*#__PURE__*/ jsx(CaretDown, {}),
children: /*#__PURE__*/ jsx(Typography, {
textColor: "success600",
variant: "omega",
fontWeight: "bold",
children: formatMessage({
id: 'content-releases.pages.ReleaseDetails.entry-validation.ready-to-publish',
defaultMessage: 'Ready to publish'
})
})
})
});
}
if (status === 'modified') {
return /*#__PURE__*/ jsx(Popover.Trigger, {
children: /*#__PURE__*/ jsx(Button, {
variant: "ghost",
startIcon: /*#__PURE__*/ jsx(ArrowsCounterClockwise, {
fill: "alternative600"
}),
endIcon: /*#__PURE__*/ jsx(CaretDown, {}),
children: /*#__PURE__*/ jsx(Typography, {
variant: "omega",
fontWeight: "bold",
textColor: "alternative600",
children: formatMessage({
id: 'content-releases.pages.ReleaseDetails.entry-validation.modified',
defaultMessage: 'Ready to publish changes'
})
})
})
});
}
return /*#__PURE__*/ jsx(Popover.Trigger, {
children: /*#__PURE__*/ jsx(Button, {
variant: "ghost",
startIcon: /*#__PURE__*/ jsx(CheckCircle, {
fill: "success600"
}),
endIcon: /*#__PURE__*/ jsx(CaretDown, {}),
children: /*#__PURE__*/ jsx(Typography, {
textColor: "success600",
variant: "omega",
fontWeight: "bold",
children: formatMessage({
id: 'content-releases.pages.ReleaseDetails.entry-validation.already-published',
defaultMessage: 'Already published'
})
})
})
});
}
if (status === 'published') {
return /*#__PURE__*/ jsx(Popover.Trigger, {
children: /*#__PURE__*/ jsx(Button, {
variant: "ghost",
startIcon: /*#__PURE__*/ jsx(CheckCircle, {
fill: "success600"
}),
endIcon: /*#__PURE__*/ jsx(CaretDown, {}),
children: /*#__PURE__*/ jsx(Typography, {
textColor: "success600",
variant: "omega",
fontWeight: "bold",
children: formatMessage({
id: 'content-releases.pages.ReleaseDetails.entry-validation.ready-to-unpublish',
defaultMessage: 'Ready to unpublish'
})
})
})
});
}
return /*#__PURE__*/ jsx(Popover.Trigger, {
children: /*#__PURE__*/ jsx(Button, {
variant: "ghost",
startIcon: /*#__PURE__*/ jsx(CheckCircle, {
fill: "success600"
}),
endIcon: /*#__PURE__*/ jsx(CaretDown, {}),
children: /*#__PURE__*/ jsx(Typography, {
textColor: "success600",
variant: "omega",
fontWeight: "bold",
children: formatMessage({
id: 'content-releases.pages.ReleaseDetails.entry-validation.already-unpublished',
defaultMessage: 'Already unpublished'
})
})
})
});
};
const FieldsValidation = ({ hasErrors, errors, kind, contentTypeUid, documentId, locale })=>{
const { formatMessage } = useIntl();
return /*#__PURE__*/ jsxs(Flex, {
direction: "column",
gap: 1,
width: "100%",
padding: 5,
children: [
/*#__PURE__*/ jsxs(Flex, {
gap: 2,
width: "100%",
children: [
/*#__PURE__*/ jsx(Typography, {
fontWeight: "bold",
children: formatMessage({
id: 'content-releases.pages.ReleaseDetails.entry-validation.fields',
defaultMessage: 'Fields'
})
}),
hasErrors ? /*#__PURE__*/ jsx(CrossCircle, {
fill: "danger600"
}) : /*#__PURE__*/ jsx(CheckCircle, {
fill: "success600"
})
]
}),
/*#__PURE__*/ jsx(Typography, {
width: "100%",
textColor: "neutral600",
children: hasErrors ? formatMessage({
id: 'content-releases.pages.ReleaseDetails.entry-validation.fields.error',
defaultMessage: '{errors} errors on fields.'
}, {
errors: errors ? Object.keys(errors).length : 0
}) : formatMessage({
id: 'content-releases.pages.ReleaseDetails.entry-validation.fields.success',
defaultMessage: 'All fields are filled correctly.'
})
}),
hasErrors && /*#__PURE__*/ jsx(LinkButton, {
tag: Link,
to: {
pathname: `/content-manager/${kind === 'collectionType' ? 'collection-types' : 'single-types'}/${contentTypeUid}/${documentId}`,
search: locale ? stringify({
plugins: {
i18n: {
locale
}
}
}) : ''
},
variant: "secondary",
fullWidth: true,
state: {
forceValidation: true
},
children: formatMessage({
id: 'content-releases.pages.ReleaseDetails.entry-validation.fields.see-errors',
defaultMessage: 'See errors'
})
})
]
});
};
const getReviewStageIcon = ({ contentTypeHasReviewWorkflow, requiredStage, entryStage })=>{
if (!contentTypeHasReviewWorkflow) {
return /*#__PURE__*/ jsx(CheckCircle, {
fill: "neutral200"
});
}
if (requiredStage && requiredStage.id !== entryStage?.id) {
return /*#__PURE__*/ jsx(CrossCircle, {
fill: "danger600"
});
}
return /*#__PURE__*/ jsx(CheckCircle, {
fill: "success600"
});
};
const getReviewStageMessage = ({ contentTypeHasReviewWorkflow, requiredStage, entryStage, formatMessage })=>{
if (!contentTypeHasReviewWorkflow) {
return formatMessage({
id: 'content-releases.pages.ReleaseDetails.entry-validation.review-stage.not-enabled',
defaultMessage: 'This entry is not associated to any workflow.'
});
}
if (requiredStage && requiredStage.id !== entryStage?.id) {
return formatMessage({
id: 'content-releases.pages.ReleaseDetails.entry-validation.review-stage.not-ready',
defaultMessage: 'This entry is not at the required stage for publishing. ({stageName})'
}, {
stageName: requiredStage?.name ?? ''
});
}
if (requiredStage && requiredStage.id === entryStage?.id) {
return formatMessage({
id: 'content-releases.pages.ReleaseDetails.entry-validation.review-stage.ready',
defaultMessage: 'This entry is at the required stage for publishing. ({stageName})'
}, {
stageName: requiredStage?.name ?? ''
});
}
return formatMessage({
id: 'content-releases.pages.ReleaseDetails.entry-validation.review-stage.stage-not-required',
defaultMessage: 'No required stage for publication'
});
};
const ReviewStageValidation = ({ contentTypeHasReviewWorkflow, requiredStage, entryStage })=>{
const { formatMessage } = useIntl();
const Icon = getReviewStageIcon({
contentTypeHasReviewWorkflow,
requiredStage,
entryStage
});
return /*#__PURE__*/ jsxs(Flex, {
direction: "column",
gap: 1,
width: "100%",
padding: 5,
children: [
/*#__PURE__*/ jsxs(Flex, {
gap: 2,
width: "100%",
children: [
/*#__PURE__*/ jsx(Typography, {
fontWeight: "bold",
children: formatMessage({
id: 'content-releases.pages.ReleaseDetails.entry-validation.review-stage',
defaultMessage: 'Review stage'
})
}),
Icon
]
}),
/*#__PURE__*/ jsx(Typography, {
textColor: "neutral600",
children: getReviewStageMessage({
contentTypeHasReviewWorkflow,
requiredStage,
entryStage,
formatMessage
})
})
]
});
};
const EntryValidationPopover = ({ schema, entry, status, action })=>{
const { validate, isLoading } = unstable_useDocument({
collectionType: schema?.kind ?? '',
model: schema?.uid ?? ''
}, {
// useDocument makes a request to get more data about the entry, but we only want to have the validation function so we skip the request
skip: true
});
// Validation errors
const errors = isLoading ? null : validate(entry);
const hasErrors = errors ? Object.keys(errors).length > 0 : false;
// Entry stage
const contentTypeHasReviewWorkflow = schema?.hasReviewWorkflow ?? false;
const requiredStage = schema?.stageRequiredToPublish;
const entryStage = entry.strapi_stage;
if (isLoading) {
return null;
}
return /*#__PURE__*/ jsxs(Popover.Root, {
children: [
/*#__PURE__*/ jsx(EntryStatusTrigger, {
action: action,
status: status,
hasErrors: hasErrors,
requiredStage: requiredStage,
entryStage: entryStage
}),
/*#__PURE__*/ jsx(Popover.Content, {
children: /*#__PURE__*/ jsxs(StyledPopoverFlex, {
direction: "column",
children: [
/*#__PURE__*/ jsx(FieldsValidation, {
hasErrors: hasErrors,
errors: errors,
contentTypeUid: schema?.uid,
kind: schema?.kind,
documentId: entry.documentId,
locale: entry.locale
}),
/*#__PURE__*/ jsx(ReviewStageValidation, {
contentTypeHasReviewWorkflow: contentTypeHasReviewWorkflow,
requiredStage: requiredStage,
entryStage: entryStage
})
]
})
})
]
});
};
export { EntryValidationPopover };
//# sourceMappingURL=EntryValidationPopover.mjs.map
@@ -0,0 +1 @@
{"version":3,"file":"EntryValidationPopover.mjs","sources":["../../../admin/src/components/EntryValidationPopover.tsx"],"sourcesContent":["import * as React from 'react';\n\nimport { FormErrors, FormValues } from '@strapi/admin/strapi-admin';\nimport { unstable_useDocument } from '@strapi/content-manager/strapi-admin';\nimport { Button, LinkButton, Flex, Typography, Popover } from '@strapi/design-system';\nimport { CheckCircle, CrossCircle, ArrowsCounterClockwise, CaretDown } from '@strapi/icons';\nimport { stringify } from 'qs';\nimport { useIntl, MessageDescriptor } from 'react-intl';\nimport { Link } from 'react-router-dom';\nimport { styled } from 'styled-components';\n\nimport type {\n ReleaseAction,\n ReleaseActionEntry,\n Stage,\n} from '../../../shared/contracts/release-actions';\nimport type { Struct } from '@strapi/types';\n\nconst StyledPopoverFlex = styled(Flex)`\n width: 100%;\n max-width: 256px;\n\n & > * {\n border-bottom: 1px solid ${({ theme }) => theme.colors.neutral150};\n }\n\n & > *:last-child {\n border-bottom: none;\n }\n`;\n\ninterface EntryValidationPopoverProps {\n action: ReleaseAction['type'];\n schema?: Struct.ContentTypeSchema & {\n hasReviewWorkflow: boolean;\n stageRequiredToPublish?: Stage;\n };\n entry: ReleaseActionEntry;\n status: ReleaseAction['status'];\n}\n\ninterface ValidationStatusProps {\n action: ReleaseAction['type'];\n status: ReleaseAction['status'];\n hasErrors: boolean | null;\n requiredStage?: Stage;\n entryStage?: Stage;\n}\n\nconst EntryStatusTrigger = ({\n action,\n status,\n hasErrors,\n requiredStage,\n entryStage,\n}: ValidationStatusProps) => {\n const { formatMessage } = useIntl();\n\n if (action === 'publish') {\n if (hasErrors || (requiredStage && requiredStage.id !== entryStage?.id)) {\n return (\n <Popover.Trigger>\n <Button\n variant=\"ghost\"\n startIcon={<CrossCircle fill=\"danger600\" />}\n endIcon={<CaretDown />}\n >\n <Typography textColor=\"danger600\" variant=\"omega\" fontWeight=\"bold\">\n {formatMessage({\n id: 'content-releases.pages.ReleaseDetails.entry-validation.not-ready',\n defaultMessage: 'Not ready to publish',\n })}\n </Typography>\n </Button>\n </Popover.Trigger>\n );\n }\n\n if (status === 'draft') {\n return (\n <Popover.Trigger>\n <Button\n variant=\"ghost\"\n startIcon={<CheckCircle fill=\"success600\" />}\n endIcon={<CaretDown />}\n >\n <Typography textColor=\"success600\" variant=\"omega\" fontWeight=\"bold\">\n {formatMessage({\n id: 'content-releases.pages.ReleaseDetails.entry-validation.ready-to-publish',\n defaultMessage: 'Ready to publish',\n })}\n </Typography>\n </Button>\n </Popover.Trigger>\n );\n }\n\n if (status === 'modified') {\n return (\n <Popover.Trigger>\n <Button\n variant=\"ghost\"\n startIcon={<ArrowsCounterClockwise fill=\"alternative600\" />}\n endIcon={<CaretDown />}\n >\n <Typography variant=\"omega\" fontWeight=\"bold\" textColor=\"alternative600\">\n {formatMessage({\n id: 'content-releases.pages.ReleaseDetails.entry-validation.modified',\n defaultMessage: 'Ready to publish changes',\n })}\n </Typography>\n </Button>\n </Popover.Trigger>\n );\n }\n\n return (\n <Popover.Trigger>\n <Button\n variant=\"ghost\"\n startIcon={<CheckCircle fill=\"success600\" />}\n endIcon={<CaretDown />}\n >\n <Typography textColor=\"success600\" variant=\"omega\" fontWeight=\"bold\">\n {formatMessage({\n id: 'content-releases.pages.ReleaseDetails.entry-validation.already-published',\n defaultMessage: 'Already published',\n })}\n </Typography>\n </Button>\n </Popover.Trigger>\n );\n }\n\n if (status === 'published') {\n return (\n <Popover.Trigger>\n <Button\n variant=\"ghost\"\n startIcon={<CheckCircle fill=\"success600\" />}\n endIcon={<CaretDown />}\n >\n <Typography textColor=\"success600\" variant=\"omega\" fontWeight=\"bold\">\n {formatMessage({\n id: 'content-releases.pages.ReleaseDetails.entry-validation.ready-to-unpublish',\n defaultMessage: 'Ready to unpublish',\n })}\n </Typography>\n </Button>\n </Popover.Trigger>\n );\n }\n\n return (\n <Popover.Trigger>\n <Button variant=\"ghost\" startIcon={<CheckCircle fill=\"success600\" />} endIcon={<CaretDown />}>\n <Typography textColor=\"success600\" variaLine truncated
@@ -0,0 +1,76 @@
'use strict';
var jsxRuntime = require('react/jsx-runtime');
var React = require('react');
var dateFns = require('date-fns');
var reactIntl = require('react-intl');
function _interopNamespaceDefault(e) {
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n.default = e;
return Object.freeze(n);
}
var React__namespace = /*#__PURE__*/_interopNamespaceDefault(React);
const intervals = [
'years',
'months',
'days',
'hours',
'minutes',
'seconds'
];
/**
* Displays the relative time between a given timestamp and the current time.
* You can display a custom message for given time intervals by passing an array of custom intervals.
*
* @example
* ```jsx
* <caption>Display "last hour" if the timestamp is less than an hour ago</caption>
* <RelativeTime
* timestamp={new Date('2021-01-01')}
* customIntervals={[
* { unit: 'hours', threshold: 1, text: 'last hour' },
* ]}
* ```
*/ const RelativeTime = /*#__PURE__*/ React__namespace.forwardRef(({ timestamp, customIntervals = [], ...restProps }, forwardedRef)=>{
const { formatRelativeTime, formatDate, formatTime } = reactIntl.useIntl();
/**
* TODO: make this auto-update, like a clock.
*/ const interval = dateFns.intervalToDuration({
start: timestamp,
end: Date.now()
});
const unit = intervals.find((intervalUnit)=>{
return interval[intervalUnit] > 0 && Object.keys(interval).includes(intervalUnit);
});
const relativeTime = dateFns.isPast(timestamp) ? -interval[unit] : interval[unit];
// Display custom text if interval is less than the threshold
const customInterval = customIntervals.find((custom)=>interval[custom.unit] < custom.threshold);
const displayText = customInterval ? customInterval.text : formatRelativeTime(relativeTime, unit, {
numeric: 'auto'
});
return /*#__PURE__*/ jsxRuntime.jsx("time", {
ref: forwardedRef,
dateTime: timestamp.toISOString(),
role: "time",
title: `${formatDate(timestamp)} ${formatTime(timestamp)}`,
...restProps,
children: displayText
});
});
exports.RelativeTime = RelativeTime;
//# sourceMappingURL=RelativeTime.js.map
@@ -0,0 +1 @@
{"version":3,"file":"RelativeTime.js","sources":["../../../admin/src/components/RelativeTime.tsx"],"sourcesContent":["import * as React from 'react';\n\nimport { Duration, intervalToDuration, isPast } from 'date-fns';\nimport { useIntl } from 'react-intl';\n\nconst intervals: Array<keyof Duration> = ['years', 'months', 'days', 'hours', 'minutes', 'seconds'];\n\ninterface CustomInterval {\n unit: keyof Duration;\n text: string;\n threshold: number;\n}\n\ninterface RelativeTimeProps extends React.ComponentPropsWithoutRef<'time'> {\n timestamp: Date;\n customIntervals?: CustomInterval[];\n}\n\n/**\n * Displays the relative time between a given timestamp and the current time.\n * You can display a custom message for given time intervals by passing an array of custom intervals.\n *\n * @example\n * ```jsx\n * <caption>Display \"last hour\" if the timestamp is less than an hour ago</caption>\n * <RelativeTime\n * timestamp={new Date('2021-01-01')}\n * customIntervals={[\n * { unit: 'hours', threshold: 1, text: 'last hour' },\n * ]}\n * ```\n */\nconst RelativeTime = React.forwardRef<HTMLTimeElement, RelativeTimeProps>(\n ({ timestamp, customIntervals = [], ...restProps }, forwardedRef) => {\n const { formatRelativeTime, formatDate, formatTime } = useIntl();\n\n /**\n * TODO: make this auto-update, like a clock.\n */\n const interval = intervalToDuration({\n start: timestamp,\n end: Date.now(),\n // see https://github.com/date-fns/date-fns/issues/2891 – No idea why it's all partial it returns it every time.\n }) as Required<Duration>;\n\n const unit = intervals.find((intervalUnit) => {\n return interval[intervalUnit] > 0 && Object.keys(interval).includes(intervalUnit);\n })!;\n\n const relativeTime = isPast(timestamp) ? -interval[unit] : interval[unit];\n\n // Display custom text if interval is less than the threshold\n const customInterval = customIntervals.find(\n (custom) => interval[custom.unit] < custom.threshold\n );\n\n const displayText = customInterval\n ? customInterval.text\n : formatRelativeTime(relativeTime, unit, { numeric: 'auto' });\n\n return (\n <time\n ref={forwardedRef}\n dateTime={timestamp.toISOString()}\n role=\"time\"\n title={`${formatDate(timestamp)} ${formatTime(timestamp)}`}\n {...restProps}\n >\n {displayText}\n </time>\n );\n }\n);\n\nexport { RelativeTime };\nexport type { CustomInterval, RelativeTimeProps };\n"],"names":["intervals","RelativeTime","React","forwardRef","timestamp","customIntervals","restProps","forwardedRef","formatRelativeTime","formatDate","formatTime","useIntl","interval","intervalToDuration","start","end","Date","now","unit","find","intervalUnit","Object","keys","includes","relativeTime","isPast","customInterval","custom","threshold","displayText","text","numeric","_jsx","time","ref","dateTime","toISOString","role","title"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAKA,MAAMA,SAAmC,GAAA;AAAC,IAAA,OAAA;AAAS,IAAA,QAAA;AAAU,IAAA,MAAA;AAAQ,IAAA,OAAA;AAAS,IAAA,SAAA;AAAW,IAAA;AAAU,CAAA;AAanG;;;;;;;;;;;;;AAaC,IACKC,MAAAA,YAAAA,iBAAeC,gBAAMC,CAAAA,UAAU,CACnC,CAAC,EAAEC,SAAS,EAAEC,eAAkB,GAAA,EAAE,EAAE,GAAGC,WAAW,EAAEC,YAAAA,GAAAA;AAClD,IAAA,MAAM,EAAEC,kBAAkB,EAAEC,UAAU,EAAEC,UAAU,EAAE,GAAGC,iBAAAA,EAAAA;AAEvD;;QAGA,MAAMC,WAAWC,0BAAmB,CAAA;QAClCC,KAAOV,EAAAA,SAAAA;AACPW,QAAAA,GAAAA,EAAKC,KAAKC,GAAG;AAEf,KAAA,CAAA;AAEA,IAAA,MAAMC,IAAOlB,GAAAA,SAAAA,CAAUmB,IAAI,CAAC,CAACC,YAAAA,GAAAA;QAC3B,OAAOR,QAAQ,CAACQ,YAAAA,CAAa,GAAG,CAAA,IAAKC,OAAOC,IAAI,CAACV,QAAUW,CAAAA,CAAAA,QAAQ,CAACH,YAAAA,CAAAA;AACtE,KAAA,CAAA;IAEA,MAAMI,YAAAA,GAAeC,cAAOrB,CAAAA,SAAAA,CAAAA,GAAa,CAACQ,QAAQ,CAACM,IAAK,CAAA,GAAGN,QAAQ,CAACM,IAAK,CAAA;;AAGzE,IAAA,MAAMQ,cAAiBrB,GAAAA,eAAAA,CAAgBc,IAAI,CACzC,CAACQ,MAAAA,GAAWf,QAAQ,CAACe,MAAOT,CAAAA,IAAI,CAAC,GAAGS,OAAOC,SAAS,CAAA;AAGtD,IAAA,MAAMC,cAAcH,cAChBA,GAAAA,cAAAA,CAAeI,IAAI,GACnBtB,kBAAAA,CAAmBgB,cAAcN,IAAM,EAAA;QAAEa,OAAS,EAAA;AAAO,KAAA,CAAA;AAE7D,IAAA,qBACEC,cAACC,CAAAA,MAAAA,EAAAA;QACCC,GAAK3B,EAAAA,YAAAA;AACL4B,QAAAA,QAAAA,EAAU/B,UAAUgC,WAAW,EAAA;QAC/BC,IAAK,EAAA,MAAA;QACLC,KAAO,EAAA,CAAC,EAAE7B,UAAWL,CAAAA,SAAAA,CAAAA,CAAW,CAAC,EAAEM,UAAAA,CAAWN,WAAW,CAAC;AACzD,QAAA,GAAGE,SAAS;AAEZuB,QAAAA,QAAAA,EAAAA;;AAGP,CAAA;;;;"}
@@ -0,0 +1,55 @@
import { jsx } from 'react/jsx-runtime';
import * as React from 'react';
import { intervalToDuration, isPast } from 'date-fns';
import { useIntl } from 'react-intl';
const intervals = [
'years',
'months',
'days',
'hours',
'minutes',
'seconds'
];
/**
* Displays the relative time between a given timestamp and the current time.
* You can display a custom message for given time intervals by passing an array of custom intervals.
*
* @example
* ```jsx
* <caption>Display "last hour" if the timestamp is less than an hour ago</caption>
* <RelativeTime
* timestamp={new Date('2021-01-01')}
* customIntervals={[
* { unit: 'hours', threshold: 1, text: 'last hour' },
* ]}
* ```
*/ const RelativeTime = /*#__PURE__*/ React.forwardRef(({ timestamp, customIntervals = [], ...restProps }, forwardedRef)=>{
const { formatRelativeTime, formatDate, formatTime } = useIntl();
/**
* TODO: make this auto-update, like a clock.
*/ const interval = intervalToDuration({
start: timestamp,
end: Date.now()
});
const unit = intervals.find((intervalUnit)=>{
return interval[intervalUnit] > 0 && Object.keys(interval).includes(intervalUnit);
});
const relativeTime = isPast(timestamp) ? -interval[unit] : interval[unit];
// Display custom text if interval is less than the threshold
const customInterval = customIntervals.find((custom)=>interval[custom.unit] < custom.threshold);
const displayText = customInterval ? customInterval.text : formatRelativeTime(relativeTime, unit, {
numeric: 'auto'
});
return /*#__PURE__*/ jsx("time", {
ref: forwardedRef,
dateTime: timestamp.toISOString(),
role: "time",
title: `${formatDate(timestamp)} ${formatTime(timestamp)}`,
...restProps,
children: displayText
});
});
export { RelativeTime };
//# sourceMappingURL=RelativeTime.mjs.map
@@ -0,0 +1 @@
{"version":3,"file":"RelativeTime.mjs","sources":["../../../admin/src/components/RelativeTime.tsx"],"sourcesContent":["import * as React from 'react';\n\nimport { Duration, intervalToDuration, isPast } from 'date-fns';\nimport { useIntl } from 'react-intl';\n\nconst intervals: Array<keyof Duration> = ['years', 'months', 'days', 'hours', 'minutes', 'seconds'];\n\ninterface CustomInterval {\n unit: keyof Duration;\n text: string;\n threshold: number;\n}\n\ninterface RelativeTimeProps extends React.ComponentPropsWithoutRef<'time'> {\n timestamp: Date;\n customIntervals?: CustomInterval[];\n}\n\n/**\n * Displays the relative time between a given timestamp and the current time.\n * You can display a custom message for given time intervals by passing an array of custom intervals.\n *\n * @example\n * ```jsx\n * <caption>Display \"last hour\" if the timestamp is less than an hour ago</caption>\n * <RelativeTime\n * timestamp={new Date('2021-01-01')}\n * customIntervals={[\n * { unit: 'hours', threshold: 1, text: 'last hour' },\n * ]}\n * ```\n */\nconst RelativeTime = React.forwardRef<HTMLTimeElement, RelativeTimeProps>(\n ({ timestamp, customIntervals = [], ...restProps }, forwardedRef) => {\n const { formatRelativeTime, formatDate, formatTime } = useIntl();\n\n /**\n * TODO: make this auto-update, like a clock.\n */\n const interval = intervalToDuration({\n start: timestamp,\n end: Date.now(),\n // see https://github.com/date-fns/date-fns/issues/2891 – No idea why it's all partial it returns it every time.\n }) as Required<Duration>;\n\n const unit = intervals.find((intervalUnit) => {\n return interval[intervalUnit] > 0 && Object.keys(interval).includes(intervalUnit);\n })!;\n\n const relativeTime = isPast(timestamp) ? -interval[unit] : interval[unit];\n\n // Display custom text if interval is less than the threshold\n const customInterval = customIntervals.find(\n (custom) => interval[custom.unit] < custom.threshold\n );\n\n const displayText = customInterval\n ? customInterval.text\n : formatRelativeTime(relativeTime, unit, { numeric: 'auto' });\n\n return (\n <time\n ref={forwardedRef}\n dateTime={timestamp.toISOString()}\n role=\"time\"\n title={`${formatDate(timestamp)} ${formatTime(timestamp)}`}\n {...restProps}\n >\n {displayText}\n </time>\n );\n }\n);\n\nexport { RelativeTime };\nexport type { CustomInterval, RelativeTimeProps };\n"],"names":["intervals","RelativeTime","React","forwardRef","timestamp","customIntervals","restProps","forwardedRef","formatRelativeTime","formatDate","formatTime","useIntl","interval","intervalToDuration","start","end","Date","now","unit","find","intervalUnit","Object","keys","includes","relativeTime","isPast","customInterval","custom","threshold","displayText","text","numeric","_jsx","time","ref","dateTime","toISOString","role","title"],"mappings":";;;;;AAKA,MAAMA,SAAmC,GAAA;AAAC,IAAA,OAAA;AAAS,IAAA,QAAA;AAAU,IAAA,MAAA;AAAQ,IAAA,OAAA;AAAS,IAAA,SAAA;AAAW,IAAA;AAAU,CAAA;AAanG;;;;;;;;;;;;;AAaC,IACKC,MAAAA,YAAAA,iBAAeC,KAAMC,CAAAA,UAAU,CACnC,CAAC,EAAEC,SAAS,EAAEC,eAAkB,GAAA,EAAE,EAAE,GAAGC,WAAW,EAAEC,YAAAA,GAAAA;AAClD,IAAA,MAAM,EAAEC,kBAAkB,EAAEC,UAAU,EAAEC,UAAU,EAAE,GAAGC,OAAAA,EAAAA;AAEvD;;QAGA,MAAMC,WAAWC,kBAAmB,CAAA;QAClCC,KAAOV,EAAAA,SAAAA;AACPW,QAAAA,GAAAA,EAAKC,KAAKC,GAAG;AAEf,KAAA,CAAA;AAEA,IAAA,MAAMC,IAAOlB,GAAAA,SAAAA,CAAUmB,IAAI,CAAC,CAACC,YAAAA,GAAAA;QAC3B,OAAOR,QAAQ,CAACQ,YAAAA,CAAa,GAAG,CAAA,IAAKC,OAAOC,IAAI,CAACV,QAAUW,CAAAA,CAAAA,QAAQ,CAACH,YAAAA,CAAAA;AACtE,KAAA,CAAA;IAEA,MAAMI,YAAAA,GAAeC,MAAOrB,CAAAA,SAAAA,CAAAA,GAAa,CAACQ,QAAQ,CAACM,IAAK,CAAA,GAAGN,QAAQ,CAACM,IAAK,CAAA;;AAGzE,IAAA,MAAMQ,cAAiBrB,GAAAA,eAAAA,CAAgBc,IAAI,CACzC,CAACQ,MAAAA,GAAWf,QAAQ,CAACe,MAAOT,CAAAA,IAAI,CAAC,GAAGS,OAAOC,SAAS,CAAA;AAGtD,IAAA,MAAMC,cAAcH,cAChBA,GAAAA,cAAAA,CAAeI,IAAI,GACnBtB,kBAAAA,CAAmBgB,cAAcN,IAAM,EAAA;QAAEa,OAAS,EAAA;AAAO,KAAA,CAAA;AAE7D,IAAA,qBACEC,GAACC,CAAAA,MAAAA,EAAAA;QACCC,GAAK3B,EAAAA,YAAAA;AACL4B,QAAAA,QAAAA,EAAU/B,UAAUgC,WAAW,EAAA;QAC/BC,IAAK,EAAA,MAAA;QACLC,KAAO,EAAA,CAAC,EAAE7B,UAAWL,CAAAA,SAAAA,CAAAA,CAAW,CAAC,EAAEM,UAAAA,CAAWN,WAAW,CAAC;AACzD,QAAA,GAAGE,SAAS;AAEZuB,QAAAA,QAAAA,EAAAA;;AAGP,CAAA;;;;"}
@@ -0,0 +1,201 @@
'use strict';
var jsxRuntime = require('react/jsx-runtime');
require('react');
var strapiAdmin = require('@strapi/admin/strapi-admin');
var designSystem = require('@strapi/design-system');
var formik = require('formik');
var reactIntl = require('react-intl');
var constants = require('../constants.js');
var release = require('../services/release.js');
var ReleaseActionModal = require('./ReleaseActionModal.js');
var ReleaseActionOptions = require('./ReleaseActionOptions.js');
const getContentPermissions = (subject)=>{
const permissions = {
publish: [
{
action: 'plugin::content-manager.explorer.publish',
subject,
id: '',
actionParameters: {},
properties: {},
conditions: []
}
]
};
return permissions;
};
const ReleaseAction = ({ documents, model })=>{
const { formatMessage } = reactIntl.useIntl();
const { toggleNotification } = strapiAdmin.useNotification();
const { formatAPIError } = strapiAdmin.useAPIErrorHandler();
const [{ query }] = strapiAdmin.useQueryParams();
const contentPermissions = getContentPermissions(model);
const { allowedActions: { canPublish } } = strapiAdmin.useRBAC(contentPermissions);
const { allowedActions: { canCreate } } = strapiAdmin.useRBAC(constants.PERMISSIONS);
// Get all the releases not published
const response = release.useGetReleasesQuery();
const releases = response.data?.data;
const [createManyReleaseActions, { isLoading }] = release.useCreateManyReleaseActionsMutation();
const documentIds = documents.map((doc)=>doc.documentId);
const handleSubmit = async (values)=>{
const locale = query.plugins?.i18n?.locale;
const releaseActionEntries = documentIds.map((entryDocumentId)=>({
type: values.type,
contentType: model,
entryDocumentId,
locale
}));
const response = await createManyReleaseActions({
body: releaseActionEntries,
params: {
releaseId: values.releaseId
}
});
if ('data' in response) {
// Handle success
const notificationMessage = formatMessage({
id: 'content-releases.content-manager-list-view.add-to-release.notification.success.message',
defaultMessage: '{entriesAlreadyInRelease} out of {totalEntries} entries were already in the release.'
}, {
entriesAlreadyInRelease: response.data.meta.entriesAlreadyInRelease,
totalEntries: response.data.meta.totalEntries
});
const notification = {
type: 'success',
title: formatMessage({
id: 'content-releases.content-manager-list-view.add-to-release.notification.success.title',
defaultMessage: 'Successfully added to release.'
}, {
entriesAlreadyInRelease: response.data.meta.entriesAlreadyInRelease,
totalEntries: response.data.meta.totalEntries
}),
message: response.data.meta.entriesAlreadyInRelease ? notificationMessage : ''
};
toggleNotification(notification);
return true;
}
if ('error' in response) {
if (strapiAdmin.isFetchError(response.error)) {
// Handle fetch error
toggleNotification({
type: 'warning',
message: formatAPIError(response.error)
});
} else {
// Handle generic error
toggleNotification({
type: 'warning',
message: formatMessage({
id: 'notification.error',
defaultMessage: 'An error occurred'
})
});
}
}
};
if (!canCreate || !canPublish) return null;
return {
actionType: 'release',
variant: 'tertiary',
label: formatMessage({
id: 'content-manager-list-view.add-to-release',
defaultMessage: 'Add to Release'
}),
dialog: {
type: 'modal',
title: formatMessage({
id: 'content-manager-list-view.add-to-release',
defaultMessage: 'Add to Release'
}),
content: ({ onClose })=>{
return /*#__PURE__*/ jsxRuntime.jsx(formik.Formik, {
onSubmit: async (values)=>{
const data = await handleSubmit(values);
if (data) {
return onClose();
}
},
validationSchema: ReleaseActionModal.RELEASE_ACTION_FORM_SCHEMA,
initialValues: ReleaseActionModal.INITIAL_VALUES,
children: ({ values, setFieldValue })=>/*#__PURE__*/ jsxRuntime.jsxs(formik.Form, {
children: [
releases?.length === 0 ? /*#__PURE__*/ jsxRuntime.jsx(ReleaseActionModal.NoReleases, {}) : /*#__PURE__*/ jsxRuntime.jsx(designSystem.Modal.Body, {
children: /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
direction: "column",
alignItems: "stretch",
gap: 2,
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Box, {
paddingBottom: 6,
children: /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Field.Root, {
required: true,
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Field.Label, {
children: formatMessage({
id: 'content-releases.content-manager-list-view.add-to-release.select-label',
defaultMessage: 'Select a release'
})
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.SingleSelect, {
placeholder: formatMessage({
id: 'content-releases.content-manager-list-view.add-to-release.select-placeholder',
defaultMessage: 'Select'
}),
onChange: (value)=>setFieldValue('releaseId', value),
value: values.releaseId,
children: releases?.map((release)=>/*#__PURE__*/ jsxRuntime.jsx(designSystem.SingleSelectOption, {
value: release.id,
children: release.name
}, release.id))
})
]
})
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Field.Label, {
children: formatMessage({
id: 'content-releases.content-manager-list-view.add-to-release.action-type-label',
defaultMessage: 'What do you want to do with these entries?'
})
}),
/*#__PURE__*/ jsxRuntime.jsx(ReleaseActionOptions.ReleaseActionOptions, {
selected: values.type,
handleChange: (e)=>setFieldValue('type', e.target.value),
name: "type"
})
]
})
}),
/*#__PURE__*/ jsxRuntime.jsxs(designSystem.Modal.Footer, {
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Button, {
onClick: onClose,
variant: "tertiary",
name: "cancel",
children: formatMessage({
id: 'content-releases.content-manager-list-view.add-to-release.cancel-button',
defaultMessage: 'Cancel'
})
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Button, {
type: "submit",
disabled: !values.releaseId,
loading: isLoading,
children: formatMessage({
id: 'content-releases.content-manager-list-view.add-to-release.continue-button',
defaultMessage: 'Continue'
})
})
]
})
]
})
});
}
}
};
};
exports.ReleaseAction = ReleaseAction;
//# sourceMappingURL=ReleaseAction.js.map
@@ -0,0 +1 @@
{"version":3,"file":"ReleaseAction.js","sources":["../../../admin/src/components/ReleaseAction.tsx"],"sourcesContent":["import * as React from 'react';\n\nimport {\n useAPIErrorHandler,\n useNotification,\n useQueryParams,\n useRBAC,\n isFetchError,\n} from '@strapi/admin/strapi-admin';\nimport {\n Box,\n Button,\n Flex,\n SingleSelect,\n SingleSelectOption,\n Modal,\n Field,\n} from '@strapi/design-system';\nimport { Formik, Form } from 'formik';\nimport { useIntl } from 'react-intl';\n\nimport { CreateManyReleaseActions } from '../../../shared/contracts/release-actions';\nimport { PERMISSIONS as releasePermissions } from '../constants';\nimport { useCreateManyReleaseActionsMutation, useGetReleasesQuery } from '../services/release';\n\nimport {\n type FormValues,\n INITIAL_VALUES,\n RELEASE_ACTION_FORM_SCHEMA,\n NoReleases,\n} from './ReleaseActionModal';\nimport { ReleaseActionOptions } from './ReleaseActionOptions';\n\nimport type { BulkActionComponent } from '@strapi/content-manager/strapi-admin';\nimport type { UID } from '@strapi/types';\n\nconst getContentPermissions = (subject: string) => {\n const permissions = {\n publish: [\n {\n action: 'plugin::content-manager.explorer.publish',\n subject,\n id: '',\n actionParameters: {},\n properties: {},\n conditions: [],\n },\n ],\n };\n\n return permissions;\n};\n\nconst ReleaseAction: BulkActionComponent = ({ documents, model }) => {\n const { formatMessage } = useIntl();\n const { toggleNotification } = useNotification();\n const { formatAPIError } = useAPIErrorHandler();\n const [{ query }] = useQueryParams<{ plugins?: { i18n?: { locale?: string } } }>();\n const contentPermissions = getContentPermissions(model);\n const {\n allowedActions: { canPublish },\n } = useRBAC(contentPermissions);\n const {\n allowedActions: { canCreate },\n } = useRBAC(releasePermissions);\n\n // Get all the releases not published\n const response = useGetReleasesQuery();\n const releases = response.data?.data;\n const [createManyReleaseActions, { isLoading }] = useCreateManyReleaseActionsMutation();\n const documentIds = documents.map((doc) => doc.documentId);\n\n const handleSubmit = async (values: FormValues) => {\n const locale = query.plugins?.i18n?.locale;\n\n const releaseActionEntries: CreateManyReleaseActions.Request['body'] = documentIds.map(\n (entryDocumentId) => ({\n type: values.type,\n contentType: model as UID.ContentType,\n entryDocumentId,\n locale,\n })\n );\n\n const response = await createManyReleaseActions({\n body: releaseActionEntries,\n params: { releaseId: values.releaseId },\n });\n\n if ('data' in response) {\n // Handle success\n\n const notificationMessage = formatMessage(\n {\n id: 'content-releases.content-manager-list-view.add-to-release.notification.success.message',\n defaultMessage:\n '{entriesAlreadyInRelease} out of {totalEntries} entries were already in the release.',\n },\n {\n entriesAlreadyInRelease: response.data.meta.entriesAlreadyInRelease,\n totalEntries: response.data.meta.totalEntries,\n }\n );\n\n const notification = {\n type: 'success' as const,\n title: formatMessage(\n {\n id: 'content-releases.content-manager-list-view.add-to-release.notification.success.title',\n defaultMessage: 'Successfully added to release.',\n },\n {\n entriesAlreadyInRelease: response.data.meta.entriesAlreadyInRelease,\n totalEntries: response.data.meta.totalEntries,\n }\n ),\n message: response.data.meta.entriesAlreadyInRelease ? notificationMessage : '',\n };\n\n toggleNotification(notification);\n\n return true;\n }\n\n if ('error' in response) {\n if (isFetchError(response.error)) {\n // Handle fetch error\n toggleNotification({\n type: 'warning',\n message: formatAPIError(response.error),\n });\n } else {\n // Handle generic error\n toggleNotification({\n type: 'warning',\n message: formatMessage({ id: 'notification.error', defaultMessage: 'An error occurred' }),\n });\n }\n }\n };\n\n if (!canCreate || !canPublish) return null;\n\n return {\n actionType: 'release',\n variant: 'tertiary',\n label: formatMessage({\n id: 'content-manager-list-view.add-to-release',\n defaultMessage: 'Add to Release',\n }),\n dialog: {\n type: 'modal',\n title: formatMessage({\n id: 'content-manager-list-view.add-to-release',\n defaultMessage: 'Add to Release',\n }),\n content: ({ onClose }) => {\n return (\n <Formik\n onSubmit={async (values) => {\n const data = await handleSubmit(values);Line truncated
@@ -0,0 +1,199 @@
import { jsx, jsxs } from 'react/jsx-runtime';
import 'react';
import { useNotification, useAPIErrorHandler, useQueryParams, useRBAC, isFetchError } from '@strapi/admin/strapi-admin';
import { Modal, Flex, Box, Field, SingleSelect, SingleSelectOption, Button } from '@strapi/design-system';
import { Formik, Form } from 'formik';
import { useIntl } from 'react-intl';
import { PERMISSIONS } from '../constants.mjs';
import { useGetReleasesQuery, useCreateManyReleaseActionsMutation } from '../services/release.mjs';
import { RELEASE_ACTION_FORM_SCHEMA, INITIAL_VALUES, NoReleases } from './ReleaseActionModal.mjs';
import { ReleaseActionOptions } from './ReleaseActionOptions.mjs';
const getContentPermissions = (subject)=>{
const permissions = {
publish: [
{
action: 'plugin::content-manager.explorer.publish',
subject,
id: '',
actionParameters: {},
properties: {},
conditions: []
}
]
};
return permissions;
};
const ReleaseAction = ({ documents, model })=>{
const { formatMessage } = useIntl();
const { toggleNotification } = useNotification();
const { formatAPIError } = useAPIErrorHandler();
const [{ query }] = useQueryParams();
const contentPermissions = getContentPermissions(model);
const { allowedActions: { canPublish } } = useRBAC(contentPermissions);
const { allowedActions: { canCreate } } = useRBAC(PERMISSIONS);
// Get all the releases not published
const response = useGetReleasesQuery();
const releases = response.data?.data;
const [createManyReleaseActions, { isLoading }] = useCreateManyReleaseActionsMutation();
const documentIds = documents.map((doc)=>doc.documentId);
const handleSubmit = async (values)=>{
const locale = query.plugins?.i18n?.locale;
const releaseActionEntries = documentIds.map((entryDocumentId)=>({
type: values.type,
contentType: model,
entryDocumentId,
locale
}));
const response = await createManyReleaseActions({
body: releaseActionEntries,
params: {
releaseId: values.releaseId
}
});
if ('data' in response) {
// Handle success
const notificationMessage = formatMessage({
id: 'content-releases.content-manager-list-view.add-to-release.notification.success.message',
defaultMessage: '{entriesAlreadyInRelease} out of {totalEntries} entries were already in the release.'
}, {
entriesAlreadyInRelease: response.data.meta.entriesAlreadyInRelease,
totalEntries: response.data.meta.totalEntries
});
const notification = {
type: 'success',
title: formatMessage({
id: 'content-releases.content-manager-list-view.add-to-release.notification.success.title',
defaultMessage: 'Successfully added to release.'
}, {
entriesAlreadyInRelease: response.data.meta.entriesAlreadyInRelease,
totalEntries: response.data.meta.totalEntries
}),
message: response.data.meta.entriesAlreadyInRelease ? notificationMessage : ''
};
toggleNotification(notification);
return true;
}
if ('error' in response) {
if (isFetchError(response.error)) {
// Handle fetch error
toggleNotification({
type: 'warning',
message: formatAPIError(response.error)
});
} else {
// Handle generic error
toggleNotification({
type: 'warning',
message: formatMessage({
id: 'notification.error',
defaultMessage: 'An error occurred'
})
});
}
}
};
if (!canCreate || !canPublish) return null;
return {
actionType: 'release',
variant: 'tertiary',
label: formatMessage({
id: 'content-manager-list-view.add-to-release',
defaultMessage: 'Add to Release'
}),
dialog: {
type: 'modal',
title: formatMessage({
id: 'content-manager-list-view.add-to-release',
defaultMessage: 'Add to Release'
}),
content: ({ onClose })=>{
return /*#__PURE__*/ jsx(Formik, {
onSubmit: async (values)=>{
const data = await handleSubmit(values);
if (data) {
return onClose();
}
},
validationSchema: RELEASE_ACTION_FORM_SCHEMA,
initialValues: INITIAL_VALUES,
children: ({ values, setFieldValue })=>/*#__PURE__*/ jsxs(Form, {
children: [
releases?.length === 0 ? /*#__PURE__*/ jsx(NoReleases, {}) : /*#__PURE__*/ jsx(Modal.Body, {
children: /*#__PURE__*/ jsxs(Flex, {
direction: "column",
alignItems: "stretch",
gap: 2,
children: [
/*#__PURE__*/ jsx(Box, {
paddingBottom: 6,
children: /*#__PURE__*/ jsxs(Field.Root, {
required: true,
children: [
/*#__PURE__*/ jsx(Field.Label, {
children: formatMessage({
id: 'content-releases.content-manager-list-view.add-to-release.select-label',
defaultMessage: 'Select a release'
})
}),
/*#__PURE__*/ jsx(SingleSelect, {
placeholder: formatMessage({
id: 'content-releases.content-manager-list-view.add-to-release.select-placeholder',
defaultMessage: 'Select'
}),
onChange: (value)=>setFieldValue('releaseId', value),
value: values.releaseId,
children: releases?.map((release)=>/*#__PURE__*/ jsx(SingleSelectOption, {
value: release.id,
children: release.name
}, release.id))
})
]
})
}),
/*#__PURE__*/ jsx(Field.Label, {
children: formatMessage({
id: 'content-releases.content-manager-list-view.add-to-release.action-type-label',
defaultMessage: 'What do you want to do with these entries?'
})
}),
/*#__PURE__*/ jsx(ReleaseActionOptions, {
selected: values.type,
handleChange: (e)=>setFieldValue('type', e.target.value),
name: "type"
})
]
})
}),
/*#__PURE__*/ jsxs(Modal.Footer, {
children: [
/*#__PURE__*/ jsx(Button, {
onClick: onClose,
variant: "tertiary",
name: "cancel",
children: formatMessage({
id: 'content-releases.content-manager-list-view.add-to-release.cancel-button',
defaultMessage: 'Cancel'
})
}),
/*#__PURE__*/ jsx(Button, {
type: "submit",
disabled: !values.releaseId,
loading: isLoading,
children: formatMessage({
id: 'content-releases.content-manager-list-view.add-to-release.continue-button',
defaultMessage: 'Continue'
})
})
]
})
]
})
});
}
}
};
};
export { ReleaseAction };
//# sourceMappingURL=ReleaseAction.mjs.map
@@ -0,0 +1 @@
{"version":3,"file":"ReleaseAction.mjs","sources":["../../../admin/src/components/ReleaseAction.tsx"],"sourcesContent":["import * as React from 'react';\n\nimport {\n useAPIErrorHandler,\n useNotification,\n useQueryParams,\n useRBAC,\n isFetchError,\n} from '@strapi/admin/strapi-admin';\nimport {\n Box,\n Button,\n Flex,\n SingleSelect,\n SingleSelectOption,\n Modal,\n Field,\n} from '@strapi/design-system';\nimport { Formik, Form } from 'formik';\nimport { useIntl } from 'react-intl';\n\nimport { CreateManyReleaseActions } from '../../../shared/contracts/release-actions';\nimport { PERMISSIONS as releasePermissions } from '../constants';\nimport { useCreateManyReleaseActionsMutation, useGetReleasesQuery } from '../services/release';\n\nimport {\n type FormValues,\n INITIAL_VALUES,\n RELEASE_ACTION_FORM_SCHEMA,\n NoReleases,\n} from './ReleaseActionModal';\nimport { ReleaseActionOptions } from './ReleaseActionOptions';\n\nimport type { BulkActionComponent } from '@strapi/content-manager/strapi-admin';\nimport type { UID } from '@strapi/types';\n\nconst getContentPermissions = (subject: string) => {\n const permissions = {\n publish: [\n {\n action: 'plugin::content-manager.explorer.publish',\n subject,\n id: '',\n actionParameters: {},\n properties: {},\n conditions: [],\n },\n ],\n };\n\n return permissions;\n};\n\nconst ReleaseAction: BulkActionComponent = ({ documents, model }) => {\n const { formatMessage } = useIntl();\n const { toggleNotification } = useNotification();\n const { formatAPIError } = useAPIErrorHandler();\n const [{ query }] = useQueryParams<{ plugins?: { i18n?: { locale?: string } } }>();\n const contentPermissions = getContentPermissions(model);\n const {\n allowedActions: { canPublish },\n } = useRBAC(contentPermissions);\n const {\n allowedActions: { canCreate },\n } = useRBAC(releasePermissions);\n\n // Get all the releases not published\n const response = useGetReleasesQuery();\n const releases = response.data?.data;\n const [createManyReleaseActions, { isLoading }] = useCreateManyReleaseActionsMutation();\n const documentIds = documents.map((doc) => doc.documentId);\n\n const handleSubmit = async (values: FormValues) => {\n const locale = query.plugins?.i18n?.locale;\n\n const releaseActionEntries: CreateManyReleaseActions.Request['body'] = documentIds.map(\n (entryDocumentId) => ({\n type: values.type,\n contentType: model as UID.ContentType,\n entryDocumentId,\n locale,\n })\n );\n\n const response = await createManyReleaseActions({\n body: releaseActionEntries,\n params: { releaseId: values.releaseId },\n });\n\n if ('data' in response) {\n // Handle success\n\n const notificationMessage = formatMessage(\n {\n id: 'content-releases.content-manager-list-view.add-to-release.notification.success.message',\n defaultMessage:\n '{entriesAlreadyInRelease} out of {totalEntries} entries were already in the release.',\n },\n {\n entriesAlreadyInRelease: response.data.meta.entriesAlreadyInRelease,\n totalEntries: response.data.meta.totalEntries,\n }\n );\n\n const notification = {\n type: 'success' as const,\n title: formatMessage(\n {\n id: 'content-releases.content-manager-list-view.add-to-release.notification.success.title',\n defaultMessage: 'Successfully added to release.',\n },\n {\n entriesAlreadyInRelease: response.data.meta.entriesAlreadyInRelease,\n totalEntries: response.data.meta.totalEntries,\n }\n ),\n message: response.data.meta.entriesAlreadyInRelease ? notificationMessage : '',\n };\n\n toggleNotification(notification);\n\n return true;\n }\n\n if ('error' in response) {\n if (isFetchError(response.error)) {\n // Handle fetch error\n toggleNotification({\n type: 'warning',\n message: formatAPIError(response.error),\n });\n } else {\n // Handle generic error\n toggleNotification({\n type: 'warning',\n message: formatMessage({ id: 'notification.error', defaultMessage: 'An error occurred' }),\n });\n }\n }\n };\n\n if (!canCreate || !canPublish) return null;\n\n return {\n actionType: 'release',\n variant: 'tertiary',\n label: formatMessage({\n id: 'content-manager-list-view.add-to-release',\n defaultMessage: 'Add to Release',\n }),\n dialog: {\n type: 'modal',\n title: formatMessage({\n id: 'content-manager-list-view.add-to-release',\n defaultMessage: 'Add to Release',\n }),\n content: ({ onClose }) => {\n return (\n <Formik\n onSubmit={async (values) => {\n const data = await handleSubmit(values)Line truncated
@@ -0,0 +1,243 @@
'use strict';
var jsxRuntime = require('react/jsx-runtime');
var React = require('react');
var strapiAdmin = require('@strapi/admin/strapi-admin');
var designSystem = require('@strapi/design-system');
var icons = require('@strapi/icons');
var reactIntl = require('react-intl');
var reactRouterDom = require('react-router-dom');
var styledComponents = require('styled-components');
var constants = require('../constants.js');
var release = require('../services/release.js');
function _interopNamespaceDefault(e) {
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n.default = e;
return Object.freeze(n);
}
var React__namespace = /*#__PURE__*/_interopNamespaceDefault(React);
const StyledMenuItem = styledComponents.styled(designSystem.Menu.Item)`
&:hover {
background: ${({ theme, $variant = 'neutral' })=>theme.colors[`${$variant}100`]};
svg {
fill: ${({ theme, $variant = 'neutral' })=>theme.colors[`${$variant}600`]};
}
a {
color: ${({ theme })=>theme.colors.neutral800};
}
}
svg {
color: ${({ theme, $variant = 'neutral' })=>theme.colors[`${$variant}500`]};
}
span {
color: ${({ theme, $variant = 'neutral' })=>theme.colors[`${$variant}800`]};
}
span,
a {
width: 100%;
}
`;
const DeleteReleaseActionItem = ({ releaseId, actionId })=>{
const { formatMessage } = reactIntl.useIntl();
const { toggleNotification } = strapiAdmin.useNotification();
const { formatAPIError } = strapiAdmin.useAPIErrorHandler();
const [deleteReleaseAction] = release.useDeleteReleaseActionMutation();
const { allowedActions: { canDeleteAction } } = strapiAdmin.useRBAC(constants.PERMISSIONS);
const handleDeleteAction = async ()=>{
const response = await deleteReleaseAction({
params: {
releaseId,
actionId
}
});
if ('data' in response) {
// Handle success
toggleNotification({
type: 'success',
message: formatMessage({
id: 'content-releases.content-manager-edit-view.remove-from-release.notification.success',
defaultMessage: 'Entry removed from release'
})
});
return;
}
if ('error' in response) {
if (strapiAdmin.isFetchError(response.error)) {
// Handle fetch error
toggleNotification({
type: 'danger',
message: formatAPIError(response.error)
});
} else {
// Handle generic error
toggleNotification({
type: 'danger',
message: formatMessage({
id: 'notification.error',
defaultMessage: 'An error occurred'
})
});
}
}
};
if (!canDeleteAction) {
return null;
}
return /*#__PURE__*/ jsxRuntime.jsx(StyledMenuItem, {
$variant: "danger",
onSelect: handleDeleteAction,
children: /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
gap: 2,
children: [
/*#__PURE__*/ jsxRuntime.jsx(icons.Cross, {
width: "1.6rem",
height: "1.6rem"
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
textColor: "danger600",
variant: "omega",
children: formatMessage({
id: 'content-releases.content-manager-edit-view.remove-from-release',
defaultMessage: 'Remove from release'
})
})
]
})
});
};
const ReleaseActionEntryLinkItem = ({ contentTypeUid, documentId, locale })=>{
const { formatMessage } = reactIntl.useIntl();
const userPermissions = strapiAdmin.useAuth('ReleaseActionEntryLinkItem', (state)=>state.permissions);
// Confirm user has permissions to access the entry for the given locale
const canUpdateEntryForLocale = React__namespace.useMemo(()=>{
const updatePermissions = userPermissions.find((permission)=>permission.subject === contentTypeUid && permission.action === 'plugin::content-manager.explorer.update');
if (!updatePermissions) {
return false;
}
return Boolean(!locale || updatePermissions.properties?.locales?.includes(locale));
}, [
contentTypeUid,
locale,
userPermissions
]);
const { allowedActions: { canUpdate: canUpdateContentType } } = strapiAdmin.useRBAC({
updateContentType: [
{
action: 'plugin::content-manager.explorer.update',
subject: contentTypeUid
}
]
});
if (!canUpdateContentType || !canUpdateEntryForLocale) {
return null;
}
return /*#__PURE__*/ jsxRuntime.jsx(StyledMenuItem, {
/* @ts-expect-error inference isn't working in DS */ tag: reactRouterDom.NavLink,
isLink: true,
to: {
pathname: `/content-manager/collection-types/${contentTypeUid}/${documentId}`,
search: locale && `?plugins[i18n][locale]=${locale}`
},
children: /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
gap: 2,
children: [
/*#__PURE__*/ jsxRuntime.jsx(icons.Pencil, {
width: "1.6rem",
height: "1.6rem"
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
variant: "omega",
children: formatMessage({
id: 'content-releases.content-manager-edit-view.edit-entry',
defaultMessage: 'Edit entry'
})
})
]
})
});
};
const EditReleaseItem = ({ releaseId })=>{
const { formatMessage } = reactIntl.useIntl();
return /* @ts-expect-error inference isn't working in DS */ /*#__PURE__*/ jsxRuntime.jsx(StyledMenuItem, {
tag: reactRouterDom.NavLink,
isLink: true,
to: `/plugins/content-releases/${releaseId}`,
children: /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
gap: 2,
children: [
/*#__PURE__*/ jsxRuntime.jsx(icons.Pencil, {
width: "1.6rem",
height: "1.6rem"
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
textColor: "neutral800",
variant: "omega",
children: formatMessage({
id: 'content-releases.content-manager-edit-view.edit-release',
defaultMessage: 'Edit release'
})
})
]
})
});
};
const Root = ({ children })=>{
const { formatMessage } = reactIntl.useIntl();
const { allowedActions } = strapiAdmin.useRBAC(constants.PERMISSIONS);
return(// A user can access the dropdown if they have permissions to delete a release-action OR update a release
allowedActions.canDeleteAction || allowedActions.canUpdate ? /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Menu.Root, {
children: [
/*#__PURE__*/ jsxRuntime.jsx(StyledMoreButton, {
variant: "tertiary",
endIcon: null,
paddingLeft: "7px",
paddingRight: "7px",
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.AccessibleIcon, {
label: formatMessage({
id: 'content-releases.content-manager-edit-view.release-action-menu',
defaultMessage: 'Release action options'
}),
children: /*#__PURE__*/ jsxRuntime.jsx(icons.More, {})
})
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Menu.Content, {
top: 1,
popoverPlacement: "bottom-end",
children: children
})
]
}) : null);
};
const StyledMoreButton = styledComponents.styled(designSystem.Menu.Trigger)`
& > span {
display: flex;
}
`;
const ReleaseActionMenu = {
Root,
EditReleaseItem,
DeleteReleaseActionItem,
ReleaseActionEntryLinkItem
};
exports.ReleaseActionMenu = ReleaseActionMenu;
//# sourceMappingURL=ReleaseActionMenu.js.map
@@ -0,0 +1 @@
{"version":3,"file":"ReleaseActionMenu.js","sources":["../../../admin/src/components/ReleaseActionMenu.tsx"],"sourcesContent":["import * as React from 'react';\n\nimport {\n useAPIErrorHandler,\n useNotification,\n useAuth,\n useRBAC,\n isFetchError,\n} from '@strapi/admin/strapi-admin';\nimport { Flex, Typography, Menu, AccessibleIcon } from '@strapi/design-system';\nimport { Cross, More, Pencil } from '@strapi/icons';\nimport { useIntl } from 'react-intl';\nimport { NavLink } from 'react-router-dom';\nimport { styled } from 'styled-components';\n\nimport { DeleteReleaseAction, ReleaseAction } from '../../../shared/contracts/release-actions';\nimport { Release } from '../../../shared/contracts/releases';\nimport { PERMISSIONS } from '../constants';\nimport { useDeleteReleaseActionMutation } from '../services/release';\n\nconst StyledMenuItem = styled(Menu.Item)<{ $variant?: 'neutral' | 'danger' }>`\n &:hover {\n background: ${({ theme, $variant = 'neutral' }) => theme.colors[`${$variant}100`]};\n\n svg {\n fill: ${({ theme, $variant = 'neutral' }) => theme.colors[`${$variant}600`]};\n }\n\n a {\n color: ${({ theme }) => theme.colors.neutral800};\n }\n }\n\n svg {\n color: ${({ theme, $variant = 'neutral' }) => theme.colors[`${$variant}500`]};\n }\n\n span {\n color: ${({ theme, $variant = 'neutral' }) => theme.colors[`${$variant}800`]};\n }\n\n span,\n a {\n width: 100%;\n }\n`;\n\n/* -------------------------------------------------------------------------------------------------\n * DeleteReleaseActionItemProps\n * -----------------------------------------------------------------------------------------------*/\ninterface DeleteReleaseActionItemProps {\n releaseId: DeleteReleaseAction.Request['params']['releaseId'];\n actionId: DeleteReleaseAction.Request['params']['actionId'];\n}\n\nconst DeleteReleaseActionItem = ({ releaseId, actionId }: DeleteReleaseActionItemProps) => {\n const { formatMessage } = useIntl();\n const { toggleNotification } = useNotification();\n const { formatAPIError } = useAPIErrorHandler();\n const [deleteReleaseAction] = useDeleteReleaseActionMutation();\n const {\n allowedActions: { canDeleteAction },\n } = useRBAC(PERMISSIONS);\n\n const handleDeleteAction = async () => {\n const response = await deleteReleaseAction({\n params: { releaseId, actionId },\n });\n\n if ('data' in response) {\n // Handle success\n toggleNotification({\n type: 'success',\n message: formatMessage({\n id: 'content-releases.content-manager-edit-view.remove-from-release.notification.success',\n defaultMessage: 'Entry removed from release',\n }),\n });\n\n return;\n }\n\n if ('error' in response) {\n if (isFetchError(response.error)) {\n // Handle fetch error\n toggleNotification({\n type: 'danger',\n message: formatAPIError(response.error),\n });\n } else {\n // Handle generic error\n toggleNotification({\n type: 'danger',\n message: formatMessage({ id: 'notification.error', defaultMessage: 'An error occurred' }),\n });\n }\n }\n };\n\n if (!canDeleteAction) {\n return null;\n }\n\n return (\n <StyledMenuItem $variant=\"danger\" onSelect={handleDeleteAction}>\n <Flex gap={2}>\n <Cross width=\"1.6rem\" height=\"1.6rem\" />\n <Typography textColor=\"danger600\" variant=\"omega\">\n {formatMessage({\n id: 'content-releases.content-manager-edit-view.remove-from-release',\n defaultMessage: 'Remove from release',\n })}\n </Typography>\n </Flex>\n </StyledMenuItem>\n );\n};\n\n/* -------------------------------------------------------------------------------------------------\n * ReleaseActionEntryLinkItem\n * -----------------------------------------------------------------------------------------------*/\ninterface ReleaseActionEntryLinkItemProps {\n contentTypeUid: ReleaseAction['contentType'];\n documentId: ReleaseAction['entry']['documentId'];\n locale: ReleaseAction['locale'];\n}\n\nconst ReleaseActionEntryLinkItem = ({\n contentTypeUid,\n documentId,\n locale,\n}: ReleaseActionEntryLinkItemProps) => {\n const { formatMessage } = useIntl();\n const userPermissions = useAuth('ReleaseActionEntryLinkItem', (state) => state.permissions);\n\n // Confirm user has permissions to access the entry for the given locale\n const canUpdateEntryForLocale = React.useMemo(() => {\n const updatePermissions = userPermissions.find(\n (permission) =>\n permission.subject === contentTypeUid &&\n permission.action === 'plugin::content-manager.explorer.update'\n );\n\n if (!updatePermissions) {\n return false;\n }\n\n return Boolean(!locale || updatePermissions.properties?.locales?.includes(locale));\n }, [contentTypeUid, locale, userPermissions]);\n\n const {\n Line truncated
@@ -0,0 +1,222 @@
import { jsx, jsxs } from 'react/jsx-runtime';
import * as React from 'react';
import { useNotification, useAPIErrorHandler, useRBAC, useAuth, isFetchError } from '@strapi/admin/strapi-admin';
import { Menu, Flex, Typography, AccessibleIcon } from '@strapi/design-system';
import { Cross, Pencil, More } from '@strapi/icons';
import { useIntl } from 'react-intl';
import { NavLink } from 'react-router-dom';
import { styled } from 'styled-components';
import { PERMISSIONS } from '../constants.mjs';
import { useDeleteReleaseActionMutation } from '../services/release.mjs';
const StyledMenuItem = styled(Menu.Item)`
&:hover {
background: ${({ theme, $variant = 'neutral' })=>theme.colors[`${$variant}100`]};
svg {
fill: ${({ theme, $variant = 'neutral' })=>theme.colors[`${$variant}600`]};
}
a {
color: ${({ theme })=>theme.colors.neutral800};
}
}
svg {
color: ${({ theme, $variant = 'neutral' })=>theme.colors[`${$variant}500`]};
}
span {
color: ${({ theme, $variant = 'neutral' })=>theme.colors[`${$variant}800`]};
}
span,
a {
width: 100%;
}
`;
const DeleteReleaseActionItem = ({ releaseId, actionId })=>{
const { formatMessage } = useIntl();
const { toggleNotification } = useNotification();
const { formatAPIError } = useAPIErrorHandler();
const [deleteReleaseAction] = useDeleteReleaseActionMutation();
const { allowedActions: { canDeleteAction } } = useRBAC(PERMISSIONS);
const handleDeleteAction = async ()=>{
const response = await deleteReleaseAction({
params: {
releaseId,
actionId
}
});
if ('data' in response) {
// Handle success
toggleNotification({
type: 'success',
message: formatMessage({
id: 'content-releases.content-manager-edit-view.remove-from-release.notification.success',
defaultMessage: 'Entry removed from release'
})
});
return;
}
if ('error' in response) {
if (isFetchError(response.error)) {
// Handle fetch error
toggleNotification({
type: 'danger',
message: formatAPIError(response.error)
});
} else {
// Handle generic error
toggleNotification({
type: 'danger',
message: formatMessage({
id: 'notification.error',
defaultMessage: 'An error occurred'
})
});
}
}
};
if (!canDeleteAction) {
return null;
}
return /*#__PURE__*/ jsx(StyledMenuItem, {
$variant: "danger",
onSelect: handleDeleteAction,
children: /*#__PURE__*/ jsxs(Flex, {
gap: 2,
children: [
/*#__PURE__*/ jsx(Cross, {
width: "1.6rem",
height: "1.6rem"
}),
/*#__PURE__*/ jsx(Typography, {
textColor: "danger600",
variant: "omega",
children: formatMessage({
id: 'content-releases.content-manager-edit-view.remove-from-release',
defaultMessage: 'Remove from release'
})
})
]
})
});
};
const ReleaseActionEntryLinkItem = ({ contentTypeUid, documentId, locale })=>{
const { formatMessage } = useIntl();
const userPermissions = useAuth('ReleaseActionEntryLinkItem', (state)=>state.permissions);
// Confirm user has permissions to access the entry for the given locale
const canUpdateEntryForLocale = React.useMemo(()=>{
const updatePermissions = userPermissions.find((permission)=>permission.subject === contentTypeUid && permission.action === 'plugin::content-manager.explorer.update');
if (!updatePermissions) {
return false;
}
return Boolean(!locale || updatePermissions.properties?.locales?.includes(locale));
}, [
contentTypeUid,
locale,
userPermissions
]);
const { allowedActions: { canUpdate: canUpdateContentType } } = useRBAC({
updateContentType: [
{
action: 'plugin::content-manager.explorer.update',
subject: contentTypeUid
}
]
});
if (!canUpdateContentType || !canUpdateEntryForLocale) {
return null;
}
return /*#__PURE__*/ jsx(StyledMenuItem, {
/* @ts-expect-error inference isn't working in DS */ tag: NavLink,
isLink: true,
to: {
pathname: `/content-manager/collection-types/${contentTypeUid}/${documentId}`,
search: locale && `?plugins[i18n][locale]=${locale}`
},
children: /*#__PURE__*/ jsxs(Flex, {
gap: 2,
children: [
/*#__PURE__*/ jsx(Pencil, {
width: "1.6rem",
height: "1.6rem"
}),
/*#__PURE__*/ jsx(Typography, {
variant: "omega",
children: formatMessage({
id: 'content-releases.content-manager-edit-view.edit-entry',
defaultMessage: 'Edit entry'
})
})
]
})
});
};
const EditReleaseItem = ({ releaseId })=>{
const { formatMessage } = useIntl();
return /* @ts-expect-error inference isn't working in DS */ /*#__PURE__*/ jsx(StyledMenuItem, {
tag: NavLink,
isLink: true,
to: `/plugins/content-releases/${releaseId}`,
children: /*#__PURE__*/ jsxs(Flex, {
gap: 2,
children: [
/*#__PURE__*/ jsx(Pencil, {
width: "1.6rem",
height: "1.6rem"
}),
/*#__PURE__*/ jsx(Typography, {
textColor: "neutral800",
variant: "omega",
children: formatMessage({
id: 'content-releases.content-manager-edit-view.edit-release',
defaultMessage: 'Edit release'
})
})
]
})
});
};
const Root = ({ children })=>{
const { formatMessage } = useIntl();
const { allowedActions } = useRBAC(PERMISSIONS);
return(// A user can access the dropdown if they have permissions to delete a release-action OR update a release
allowedActions.canDeleteAction || allowedActions.canUpdate ? /*#__PURE__*/ jsxs(Menu.Root, {
children: [
/*#__PURE__*/ jsx(StyledMoreButton, {
variant: "tertiary",
endIcon: null,
paddingLeft: "7px",
paddingRight: "7px",
children: /*#__PURE__*/ jsx(AccessibleIcon, {
label: formatMessage({
id: 'content-releases.content-manager-edit-view.release-action-menu',
defaultMessage: 'Release action options'
}),
children: /*#__PURE__*/ jsx(More, {})
})
}),
/*#__PURE__*/ jsx(Menu.Content, {
top: 1,
popoverPlacement: "bottom-end",
children: children
})
]
}) : null);
};
const StyledMoreButton = styled(Menu.Trigger)`
& > span {
display: flex;
}
`;
const ReleaseActionMenu = {
Root,
EditReleaseItem,
DeleteReleaseActionItem,
ReleaseActionEntryLinkItem
};
export { ReleaseActionMenu };
//# sourceMappingURL=ReleaseActionMenu.mjs.map
@@ -0,0 +1 @@
{"version":3,"file":"ReleaseActionMenu.mjs","sources":["../../../admin/src/components/ReleaseActionMenu.tsx"],"sourcesContent":["import * as React from 'react';\n\nimport {\n useAPIErrorHandler,\n useNotification,\n useAuth,\n useRBAC,\n isFetchError,\n} from '@strapi/admin/strapi-admin';\nimport { Flex, Typography, Menu, AccessibleIcon } from '@strapi/design-system';\nimport { Cross, More, Pencil } from '@strapi/icons';\nimport { useIntl } from 'react-intl';\nimport { NavLink } from 'react-router-dom';\nimport { styled } from 'styled-components';\n\nimport { DeleteReleaseAction, ReleaseAction } from '../../../shared/contracts/release-actions';\nimport { Release } from '../../../shared/contracts/releases';\nimport { PERMISSIONS } from '../constants';\nimport { useDeleteReleaseActionMutation } from '../services/release';\n\nconst StyledMenuItem = styled(Menu.Item)<{ $variant?: 'neutral' | 'danger' }>`\n &:hover {\n background: ${({ theme, $variant = 'neutral' }) => theme.colors[`${$variant}100`]};\n\n svg {\n fill: ${({ theme, $variant = 'neutral' }) => theme.colors[`${$variant}600`]};\n }\n\n a {\n color: ${({ theme }) => theme.colors.neutral800};\n }\n }\n\n svg {\n color: ${({ theme, $variant = 'neutral' }) => theme.colors[`${$variant}500`]};\n }\n\n span {\n color: ${({ theme, $variant = 'neutral' }) => theme.colors[`${$variant}800`]};\n }\n\n span,\n a {\n width: 100%;\n }\n`;\n\n/* -------------------------------------------------------------------------------------------------\n * DeleteReleaseActionItemProps\n * -----------------------------------------------------------------------------------------------*/\ninterface DeleteReleaseActionItemProps {\n releaseId: DeleteReleaseAction.Request['params']['releaseId'];\n actionId: DeleteReleaseAction.Request['params']['actionId'];\n}\n\nconst DeleteReleaseActionItem = ({ releaseId, actionId }: DeleteReleaseActionItemProps) => {\n const { formatMessage } = useIntl();\n const { toggleNotification } = useNotification();\n const { formatAPIError } = useAPIErrorHandler();\n const [deleteReleaseAction] = useDeleteReleaseActionMutation();\n const {\n allowedActions: { canDeleteAction },\n } = useRBAC(PERMISSIONS);\n\n const handleDeleteAction = async () => {\n const response = await deleteReleaseAction({\n params: { releaseId, actionId },\n });\n\n if ('data' in response) {\n // Handle success\n toggleNotification({\n type: 'success',\n message: formatMessage({\n id: 'content-releases.content-manager-edit-view.remove-from-release.notification.success',\n defaultMessage: 'Entry removed from release',\n }),\n });\n\n return;\n }\n\n if ('error' in response) {\n if (isFetchError(response.error)) {\n // Handle fetch error\n toggleNotification({\n type: 'danger',\n message: formatAPIError(response.error),\n });\n } else {\n // Handle generic error\n toggleNotification({\n type: 'danger',\n message: formatMessage({ id: 'notification.error', defaultMessage: 'An error occurred' }),\n });\n }\n }\n };\n\n if (!canDeleteAction) {\n return null;\n }\n\n return (\n <StyledMenuItem $variant=\"danger\" onSelect={handleDeleteAction}>\n <Flex gap={2}>\n <Cross width=\"1.6rem\" height=\"1.6rem\" />\n <Typography textColor=\"danger600\" variant=\"omega\">\n {formatMessage({\n id: 'content-releases.content-manager-edit-view.remove-from-release',\n defaultMessage: 'Remove from release',\n })}\n </Typography>\n </Flex>\n </StyledMenuItem>\n );\n};\n\n/* -------------------------------------------------------------------------------------------------\n * ReleaseActionEntryLinkItem\n * -----------------------------------------------------------------------------------------------*/\ninterface ReleaseActionEntryLinkItemProps {\n contentTypeUid: ReleaseAction['contentType'];\n documentId: ReleaseAction['entry']['documentId'];\n locale: ReleaseAction['locale'];\n}\n\nconst ReleaseActionEntryLinkItem = ({\n contentTypeUid,\n documentId,\n locale,\n}: ReleaseActionEntryLinkItemProps) => {\n const { formatMessage } = useIntl();\n const userPermissions = useAuth('ReleaseActionEntryLinkItem', (state) => state.permissions);\n\n // Confirm user has permissions to access the entry for the given locale\n const canUpdateEntryForLocale = React.useMemo(() => {\n const updatePermissions = userPermissions.find(\n (permission) =>\n permission.subject === contentTypeUid &&\n permission.action === 'plugin::content-manager.explorer.update'\n );\n\n if (!updatePermissions) {\n return false;\n }\n\n return Boolean(!locale || updatePermissions.properties?.locales?.includes(locale));\n }, [contentTypeUid, locale, userPermissions]);\n\n const {\nLine truncated
@@ -0,0 +1,268 @@
'use strict';
var jsxRuntime = require('react/jsx-runtime');
require('react');
var strapiAdmin = require('@strapi/admin/strapi-admin');
var strapiAdmin$1 = require('@strapi/content-manager/strapi-admin');
var designSystem = require('@strapi/design-system');
var icons = require('@strapi/icons');
var symbols = require('@strapi/icons/symbols');
var formik = require('formik');
var reactIntl = require('react-intl');
var reactRouterDom = require('react-router-dom');
var yup = require('yup');
var constants = require('../constants.js');
var release = require('../services/release.js');
var ReleaseActionOptions = require('./ReleaseActionOptions.js');
function _interopNamespaceDefault(e) {
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n.default = e;
return Object.freeze(n);
}
var yup__namespace = /*#__PURE__*/_interopNamespaceDefault(yup);
/* -------------------------------------------------------------------------------------------------
* AddActionToReleaseModal
* -----------------------------------------------------------------------------------------------*/ const RELEASE_ACTION_FORM_SCHEMA = yup__namespace.object().shape({
type: yup__namespace.string().oneOf([
'publish',
'unpublish'
]).required(),
releaseId: yup__namespace.string().required()
});
const INITIAL_VALUES = {
type: 'publish',
releaseId: ''
};
const NoReleases = ()=>{
const { formatMessage } = reactIntl.useIntl();
return /*#__PURE__*/ jsxRuntime.jsx(designSystem.EmptyStateLayout, {
icon: /*#__PURE__*/ jsxRuntime.jsx(symbols.EmptyDocuments, {
width: "16rem"
}),
content: formatMessage({
id: 'content-releases.content-manager-edit-view.add-to-release.no-releases-message',
defaultMessage: 'No available releases. Open the list of releases and create a new one from there.'
}),
action: /*#__PURE__*/ jsxRuntime.jsx(designSystem.LinkButton, {
to: {
pathname: '/plugins/content-releases'
},
tag: reactRouterDom.Link,
variant: "secondary",
children: formatMessage({
id: 'content-releases.content-manager-edit-view.add-to-release.redirect-button',
defaultMessage: 'Open the list of releases'
})
}),
shadow: "none"
});
};
const AddActionToReleaseModal = ({ contentType, documentId, onInputChange, values })=>{
const { formatMessage } = reactIntl.useIntl();
const [{ query }] = strapiAdmin.useQueryParams();
const locale = query.plugins?.i18n?.locale;
// Get all 'pending' releases that do not have the entry attached
const response = release.useGetReleasesForEntryQuery({
contentType,
entryDocumentId: documentId,
hasEntryAttached: false,
locale
});
const releases = response.data?.data;
if (releases?.length === 0) {
return /*#__PURE__*/ jsxRuntime.jsx(NoReleases, {});
}
return /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
direction: "column",
alignItems: "stretch",
gap: 2,
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Box, {
paddingBottom: 6,
children: /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Field.Root, {
required: true,
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Field.Label, {
children: formatMessage({
id: 'content-releases.content-manager-edit-view.add-to-release.select-label',
defaultMessage: 'Select a release'
})
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.SingleSelect, {
required: true,
placeholder: formatMessage({
id: 'content-releases.content-manager-edit-view.add-to-release.select-placeholder',
defaultMessage: 'Select'
}),
name: "releaseId",
onChange: (value)=>onInputChange('releaseId', value),
value: values.releaseId,
children: releases?.map((release)=>/*#__PURE__*/ jsxRuntime.jsx(designSystem.SingleSelectOption, {
value: release.id,
children: release.name
}, release.id))
})
]
})
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Field.Label, {
children: formatMessage({
id: 'content-releases.content-manager-edit-view.add-to-release.action-type-label',
defaultMessage: 'What do you want to do with this entry?'
})
}),
/*#__PURE__*/ jsxRuntime.jsx(ReleaseActionOptions.ReleaseActionOptions, {
selected: values.type,
handleChange: (e)=>onInputChange('type', e.target.value),
name: "type"
})
]
});
};
/* -------------------------------------------------------------------------------------------------
* ReleaseActionModalForm
* -----------------------------------------------------------------------------------------------*/ const ReleaseActionModalForm = ({ documentId, document, model, collectionType })=>{
const { formatMessage } = reactIntl.useIntl();
const { allowedActions } = strapiAdmin.useRBAC(constants.PERMISSIONS);
const { canCreateAction } = allowedActions;
const [createReleaseAction, { isLoading }] = release.useCreateReleaseActionMutation();
const { toggleNotification } = strapiAdmin.useNotification();
const { formatAPIError } = strapiAdmin.useAPIErrorHandler();
const [{ query }] = strapiAdmin.useQueryParams();
const locale = query.plugins?.i18n?.locale;
const handleSubmit = async (e, onClose)=>{
try {
await formik$1.handleSubmit(e);
onClose();
} catch (error) {
if (strapiAdmin.isFetchError(error)) {
// Handle axios error
toggleNotification({
type: 'danger',
message: formatAPIError(error)
});
} else {
// Handle generic error
toggleNotification({
type: 'danger',
message: formatMessage({
id: 'notification.error',
defaultMessage: 'An error occurred'
})
});
}
}
};
const formik$1 = formik.useFormik({
initialValues: INITIAL_VALUES,
validationSchema: RELEASE_ACTION_FORM_SCHEMA,
onSubmit: async (values)=>{
if (collectionType === 'collection-types' && !documentId) {
throw new Error('Document id is required');
}
const response = await createReleaseAction({
body: {
type: values.type,
contentType: model,
entryDocumentId: documentId,
locale
},
params: {
releaseId: values.releaseId
}
});
if ('data' in response) {
// Handle success
toggleNotification({
type: 'success',
message: formatMessage({
id: 'content-releases.content-manager-edit-view.add-to-release.notification.success',
defaultMessage: 'Entry added to release'
})
});
return;
}
if ('error' in response) {
throw response.error;
}
}
});
const { edit: { options } } = strapiAdmin$1.unstable_useDocumentLayout(model);
// Project is not EE or contentType does not have draftAndPublish enabled
if (!window.strapi.isEE || !options?.draftAndPublish || !canCreateAction) {
return null;
}
if (collectionType === 'collection-types' && (!documentId || documentId === 'create')) {
return null;
}
return {
label: formatMessage({
id: 'content-releases.content-manager-edit-view.add-to-release',
defaultMessage: 'Add to release'
}),
icon: /*#__PURE__*/ jsxRuntime.jsx(icons.PaperPlane, {}),
// Entry is creating so we don't want to allow adding it to a release
disabled: !document,
position: [
'panel',
'table-row'
],
dialog: {
type: 'modal',
title: formatMessage({
id: 'content-releases.content-manager-edit-view.add-to-release',
defaultMessage: 'Add to release'
}),
content: /*#__PURE__*/ jsxRuntime.jsx(AddActionToReleaseModal, {
contentType: model,
documentId: documentId,
onInputChange: formik$1.setFieldValue,
values: formik$1.values
}),
footer: ({ onClose })=>/*#__PURE__*/ jsxRuntime.jsxs(designSystem.Modal.Footer, {
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Button, {
onClick: onClose,
variant: "tertiary",
name: "cancel",
children: formatMessage({
id: 'content-releases.content-manager-edit-view.add-to-release.cancel-button',
defaultMessage: 'Cancel'
})
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Button, {
type: "submit",
// @ts-expect-error - formik ReactEvent types don't match button onClick types as they expect a MouseEvent
onClick: (e)=>handleSubmit(e, onClose),
disabled: !formik$1.values.releaseId,
loading: isLoading,
children: formatMessage({
id: 'content-releases.content-manager-edit-view.add-to-release.continue-button',
defaultMessage: 'Continue'
})
})
]
})
}
};
};
exports.INITIAL_VALUES = INITIAL_VALUES;
exports.NoReleases = NoReleases;
exports.RELEASE_ACTION_FORM_SCHEMA = RELEASE_ACTION_FORM_SCHEMA;
exports.ReleaseActionModalForm = ReleaseActionModalForm;
//# sourceMappingURL=ReleaseActionModal.js.map
@@ -0,0 +1 @@
{"version":3,"file":"ReleaseActionModal.js","sources":["../../../admin/src/components/ReleaseActionModal.tsx"],"sourcesContent":["import * as React from 'react';\n\nimport {\n useAPIErrorHandler,\n useNotification,\n useQueryParams,\n useRBAC,\n isFetchError,\n} from '@strapi/admin/strapi-admin';\nimport { unstable_useDocumentLayout as useDocumentLayout } from '@strapi/content-manager/strapi-admin';\nimport {\n Box,\n Button,\n Flex,\n SingleSelect,\n SingleSelectOption,\n EmptyStateLayout,\n LinkButton,\n Field,\n Modal,\n} from '@strapi/design-system';\nimport { PaperPlane } from '@strapi/icons';\nimport { EmptyDocuments } from '@strapi/icons/symbols';\nimport { useFormik } from 'formik';\nimport { useIntl } from 'react-intl';\nimport { Link as ReactRouterLink } from 'react-router-dom';\nimport * as yup from 'yup';\n\nimport { CreateReleaseAction } from '../../../shared/contracts/release-actions';\nimport { PERMISSIONS } from '../constants';\nimport { useCreateReleaseActionMutation, useGetReleasesForEntryQuery } from '../services/release';\n\nimport { ReleaseActionOptions } from './ReleaseActionOptions';\n\nimport type {\n DocumentActionComponent,\n DocumentActionProps,\n} from '@strapi/content-manager/strapi-admin';\nimport type { UID } from '@strapi/types';\n\n/* -------------------------------------------------------------------------------------------------\n * AddActionToReleaseModal\n * -----------------------------------------------------------------------------------------------*/\nexport const RELEASE_ACTION_FORM_SCHEMA = yup.object().shape({\n type: yup.string().oneOf(['publish', 'unpublish']).required(),\n releaseId: yup.string().required(),\n});\n\nexport interface FormValues {\n type: CreateReleaseAction.Request['body']['type'];\n releaseId: CreateReleaseAction.Request['params']['releaseId'];\n}\n\nexport const INITIAL_VALUES = {\n type: 'publish',\n releaseId: '',\n} satisfies FormValues;\n\ninterface AddActionToReleaseModalProps {\n contentType: string;\n documentId?: string;\n onInputChange: (field: keyof FormValues, value: string | number) => void;\n values: FormValues;\n}\n\nexport const NoReleases = () => {\n const { formatMessage } = useIntl();\n return (\n <EmptyStateLayout\n icon={<EmptyDocuments width=\"16rem\" />}\n content={formatMessage({\n id: 'content-releases.content-manager-edit-view.add-to-release.no-releases-message',\n defaultMessage:\n 'No available releases. Open the list of releases and create a new one from there.',\n })}\n action={\n <LinkButton\n to={{\n pathname: '/plugins/content-releases',\n }}\n tag={ReactRouterLink}\n variant=\"secondary\"\n >\n {formatMessage({\n id: 'content-releases.content-manager-edit-view.add-to-release.redirect-button',\n defaultMessage: 'Open the list of releases',\n })}\n </LinkButton>\n }\n shadow=\"none\"\n />\n );\n};\n\nconst AddActionToReleaseModal = ({\n contentType,\n documentId,\n onInputChange,\n values,\n}: AddActionToReleaseModalProps) => {\n const { formatMessage } = useIntl();\n const [{ query }] = useQueryParams<{ plugins?: { i18n?: { locale?: string } } }>();\n const locale = query.plugins?.i18n?.locale;\n\n // Get all 'pending' releases that do not have the entry attached\n const response = useGetReleasesForEntryQuery({\n contentType,\n entryDocumentId: documentId,\n hasEntryAttached: false,\n locale,\n });\n\n const releases = response.data?.data;\n\n if (releases?.length === 0) {\n return <NoReleases />;\n }\n\n return (\n <Flex direction=\"column\" alignItems=\"stretch\" gap={2}>\n <Box paddingBottom={6}>\n <Field.Root required>\n <Field.Label>\n {formatMessage({\n id: 'content-releases.content-manager-edit-view.add-to-release.select-label',\n defaultMessage: 'Select a release',\n })}\n </Field.Label>\n <SingleSelect\n required\n placeholder={formatMessage({\n id: 'content-releases.content-manager-edit-view.add-to-release.select-placeholder',\n defaultMessage: 'Select',\n })}\n name=\"releaseId\"\n onChange={(value) => onInputChange('releaseId', value)}\n value={values.releaseId}\n >\n {releases?.map((release) => (\n <SingleSelectOption key={release.id} value={release.id}>\n {release.name}\n </SingleSelectOption>\n ))}\n </SingleSelect>\n </Field.Root>\n </Box>\n <Field.Label>\n {formatMessage({\n id: 'content-releases.content-manager-edit-view.add-to-release.action-type-label',\n defaultMessage: 'What do you want to do with this entry?',\n })}\n </Field.Label>\n <ReleaseActionOptLine truncated
@@ -0,0 +1,244 @@
import { jsx, jsxs } from 'react/jsx-runtime';
import 'react';
import { useRBAC, useNotification, useAPIErrorHandler, useQueryParams, isFetchError } from '@strapi/admin/strapi-admin';
import { unstable_useDocumentLayout } from '@strapi/content-manager/strapi-admin';
import { Modal, Button, EmptyStateLayout, LinkButton, Flex, Box, Field, SingleSelect, SingleSelectOption } from '@strapi/design-system';
import { PaperPlane } from '@strapi/icons';
import { EmptyDocuments } from '@strapi/icons/symbols';
import { useFormik } from 'formik';
import { useIntl } from 'react-intl';
import { Link } from 'react-router-dom';
import * as yup from 'yup';
import { PERMISSIONS } from '../constants.mjs';
import { useCreateReleaseActionMutation, useGetReleasesForEntryQuery } from '../services/release.mjs';
import { ReleaseActionOptions } from './ReleaseActionOptions.mjs';
/* -------------------------------------------------------------------------------------------------
* AddActionToReleaseModal
* -----------------------------------------------------------------------------------------------*/ const RELEASE_ACTION_FORM_SCHEMA = yup.object().shape({
type: yup.string().oneOf([
'publish',
'unpublish'
]).required(),
releaseId: yup.string().required()
});
const INITIAL_VALUES = {
type: 'publish',
releaseId: ''
};
const NoReleases = ()=>{
const { formatMessage } = useIntl();
return /*#__PURE__*/ jsx(EmptyStateLayout, {
icon: /*#__PURE__*/ jsx(EmptyDocuments, {
width: "16rem"
}),
content: formatMessage({
id: 'content-releases.content-manager-edit-view.add-to-release.no-releases-message',
defaultMessage: 'No available releases. Open the list of releases and create a new one from there.'
}),
action: /*#__PURE__*/ jsx(LinkButton, {
to: {
pathname: '/plugins/content-releases'
},
tag: Link,
variant: "secondary",
children: formatMessage({
id: 'content-releases.content-manager-edit-view.add-to-release.redirect-button',
defaultMessage: 'Open the list of releases'
})
}),
shadow: "none"
});
};
const AddActionToReleaseModal = ({ contentType, documentId, onInputChange, values })=>{
const { formatMessage } = useIntl();
const [{ query }] = useQueryParams();
const locale = query.plugins?.i18n?.locale;
// Get all 'pending' releases that do not have the entry attached
const response = useGetReleasesForEntryQuery({
contentType,
entryDocumentId: documentId,
hasEntryAttached: false,
locale
});
const releases = response.data?.data;
if (releases?.length === 0) {
return /*#__PURE__*/ jsx(NoReleases, {});
}
return /*#__PURE__*/ jsxs(Flex, {
direction: "column",
alignItems: "stretch",
gap: 2,
children: [
/*#__PURE__*/ jsx(Box, {
paddingBottom: 6,
children: /*#__PURE__*/ jsxs(Field.Root, {
required: true,
children: [
/*#__PURE__*/ jsx(Field.Label, {
children: formatMessage({
id: 'content-releases.content-manager-edit-view.add-to-release.select-label',
defaultMessage: 'Select a release'
})
}),
/*#__PURE__*/ jsx(SingleSelect, {
required: true,
placeholder: formatMessage({
id: 'content-releases.content-manager-edit-view.add-to-release.select-placeholder',
defaultMessage: 'Select'
}),
name: "releaseId",
onChange: (value)=>onInputChange('releaseId', value),
value: values.releaseId,
children: releases?.map((release)=>/*#__PURE__*/ jsx(SingleSelectOption, {
value: release.id,
children: release.name
}, release.id))
})
]
})
}),
/*#__PURE__*/ jsx(Field.Label, {
children: formatMessage({
id: 'content-releases.content-manager-edit-view.add-to-release.action-type-label',
defaultMessage: 'What do you want to do with this entry?'
})
}),
/*#__PURE__*/ jsx(ReleaseActionOptions, {
selected: values.type,
handleChange: (e)=>onInputChange('type', e.target.value),
name: "type"
})
]
});
};
/* -------------------------------------------------------------------------------------------------
* ReleaseActionModalForm
* -----------------------------------------------------------------------------------------------*/ const ReleaseActionModalForm = ({ documentId, document, model, collectionType })=>{
const { formatMessage } = useIntl();
const { allowedActions } = useRBAC(PERMISSIONS);
const { canCreateAction } = allowedActions;
const [createReleaseAction, { isLoading }] = useCreateReleaseActionMutation();
const { toggleNotification } = useNotification();
const { formatAPIError } = useAPIErrorHandler();
const [{ query }] = useQueryParams();
const locale = query.plugins?.i18n?.locale;
const handleSubmit = async (e, onClose)=>{
try {
await formik.handleSubmit(e);
onClose();
} catch (error) {
if (isFetchError(error)) {
// Handle axios error
toggleNotification({
type: 'danger',
message: formatAPIError(error)
});
} else {
// Handle generic error
toggleNotification({
type: 'danger',
message: formatMessage({
id: 'notification.error',
defaultMessage: 'An error occurred'
})
});
}
}
};
const formik = useFormik({
initialValues: INITIAL_VALUES,
validationSchema: RELEASE_ACTION_FORM_SCHEMA,
onSubmit: async (values)=>{
if (collectionType === 'collection-types' && !documentId) {
throw new Error('Document id is required');
}
const response = await createReleaseAction({
body: {
type: values.type,
contentType: model,
entryDocumentId: documentId,
locale
},
params: {
releaseId: values.releaseId
}
});
if ('data' in response) {
// Handle success
toggleNotification({
type: 'success',
message: formatMessage({
id: 'content-releases.content-manager-edit-view.add-to-release.notification.success',
defaultMessage: 'Entry added to release'
})
});
return;
}
if ('error' in response) {
throw response.error;
}
}
});
const { edit: { options } } = unstable_useDocumentLayout(model);
// Project is not EE or contentType does not have draftAndPublish enabled
if (!window.strapi.isEE || !options?.draftAndPublish || !canCreateAction) {
return null;
}
if (collectionType === 'collection-types' && (!documentId || documentId === 'create')) {
return null;
}
return {
label: formatMessage({
id: 'content-releases.content-manager-edit-view.add-to-release',
defaultMessage: 'Add to release'
}),
icon: /*#__PURE__*/ jsx(PaperPlane, {}),
// Entry is creating so we don't want to allow adding it to a release
disabled: !document,
position: [
'panel',
'table-row'
],
dialog: {
type: 'modal',
title: formatMessage({
id: 'content-releases.content-manager-edit-view.add-to-release',
defaultMessage: 'Add to release'
}),
content: /*#__PURE__*/ jsx(AddActionToReleaseModal, {
contentType: model,
documentId: documentId,
onInputChange: formik.setFieldValue,
values: formik.values
}),
footer: ({ onClose })=>/*#__PURE__*/ jsxs(Modal.Footer, {
children: [
/*#__PURE__*/ jsx(Button, {
onClick: onClose,
variant: "tertiary",
name: "cancel",
children: formatMessage({
id: 'content-releases.content-manager-edit-view.add-to-release.cancel-button',
defaultMessage: 'Cancel'
})
}),
/*#__PURE__*/ jsx(Button, {
type: "submit",
// @ts-expect-error - formik ReactEvent types don't match button onClick types as they expect a MouseEvent
onClick: (e)=>handleSubmit(e, onClose),
disabled: !formik.values.releaseId,
loading: isLoading,
children: formatMessage({
id: 'content-releases.content-manager-edit-view.add-to-release.continue-button',
defaultMessage: 'Continue'
})
})
]
})
}
};
};
export { INITIAL_VALUES, NoReleases, RELEASE_ACTION_FORM_SCHEMA, ReleaseActionModalForm };
//# sourceMappingURL=ReleaseActionModal.mjs.map
@@ -0,0 +1 @@
{"version":3,"file":"ReleaseActionModal.mjs","sources":["../../../admin/src/components/ReleaseActionModal.tsx"],"sourcesContent":["import * as React from 'react';\n\nimport {\n useAPIErrorHandler,\n useNotification,\n useQueryParams,\n useRBAC,\n isFetchError,\n} from '@strapi/admin/strapi-admin';\nimport { unstable_useDocumentLayout as useDocumentLayout } from '@strapi/content-manager/strapi-admin';\nimport {\n Box,\n Button,\n Flex,\n SingleSelect,\n SingleSelectOption,\n EmptyStateLayout,\n LinkButton,\n Field,\n Modal,\n} from '@strapi/design-system';\nimport { PaperPlane } from '@strapi/icons';\nimport { EmptyDocuments } from '@strapi/icons/symbols';\nimport { useFormik } from 'formik';\nimport { useIntl } from 'react-intl';\nimport { Link as ReactRouterLink } from 'react-router-dom';\nimport * as yup from 'yup';\n\nimport { CreateReleaseAction } from '../../../shared/contracts/release-actions';\nimport { PERMISSIONS } from '../constants';\nimport { useCreateReleaseActionMutation, useGetReleasesForEntryQuery } from '../services/release';\n\nimport { ReleaseActionOptions } from './ReleaseActionOptions';\n\nimport type {\n DocumentActionComponent,\n DocumentActionProps,\n} from '@strapi/content-manager/strapi-admin';\nimport type { UID } from '@strapi/types';\n\n/* -------------------------------------------------------------------------------------------------\n * AddActionToReleaseModal\n * -----------------------------------------------------------------------------------------------*/\nexport const RELEASE_ACTION_FORM_SCHEMA = yup.object().shape({\n type: yup.string().oneOf(['publish', 'unpublish']).required(),\n releaseId: yup.string().required(),\n});\n\nexport interface FormValues {\n type: CreateReleaseAction.Request['body']['type'];\n releaseId: CreateReleaseAction.Request['params']['releaseId'];\n}\n\nexport const INITIAL_VALUES = {\n type: 'publish',\n releaseId: '',\n} satisfies FormValues;\n\ninterface AddActionToReleaseModalProps {\n contentType: string;\n documentId?: string;\n onInputChange: (field: keyof FormValues, value: string | number) => void;\n values: FormValues;\n}\n\nexport const NoReleases = () => {\n const { formatMessage } = useIntl();\n return (\n <EmptyStateLayout\n icon={<EmptyDocuments width=\"16rem\" />}\n content={formatMessage({\n id: 'content-releases.content-manager-edit-view.add-to-release.no-releases-message',\n defaultMessage:\n 'No available releases. Open the list of releases and create a new one from there.',\n })}\n action={\n <LinkButton\n to={{\n pathname: '/plugins/content-releases',\n }}\n tag={ReactRouterLink}\n variant=\"secondary\"\n >\n {formatMessage({\n id: 'content-releases.content-manager-edit-view.add-to-release.redirect-button',\n defaultMessage: 'Open the list of releases',\n })}\n </LinkButton>\n }\n shadow=\"none\"\n />\n );\n};\n\nconst AddActionToReleaseModal = ({\n contentType,\n documentId,\n onInputChange,\n values,\n}: AddActionToReleaseModalProps) => {\n const { formatMessage } = useIntl();\n const [{ query }] = useQueryParams<{ plugins?: { i18n?: { locale?: string } } }>();\n const locale = query.plugins?.i18n?.locale;\n\n // Get all 'pending' releases that do not have the entry attached\n const response = useGetReleasesForEntryQuery({\n contentType,\n entryDocumentId: documentId,\n hasEntryAttached: false,\n locale,\n });\n\n const releases = response.data?.data;\n\n if (releases?.length === 0) {\n return <NoReleases />;\n }\n\n return (\n <Flex direction=\"column\" alignItems=\"stretch\" gap={2}>\n <Box paddingBottom={6}>\n <Field.Root required>\n <Field.Label>\n {formatMessage({\n id: 'content-releases.content-manager-edit-view.add-to-release.select-label',\n defaultMessage: 'Select a release',\n })}\n </Field.Label>\n <SingleSelect\n required\n placeholder={formatMessage({\n id: 'content-releases.content-manager-edit-view.add-to-release.select-placeholder',\n defaultMessage: 'Select',\n })}\n name=\"releaseId\"\n onChange={(value) => onInputChange('releaseId', value)}\n value={values.releaseId}\n >\n {releases?.map((release) => (\n <SingleSelectOption key={release.id} value={release.id}>\n {release.name}\n </SingleSelectOption>\n ))}\n </SingleSelect>\n </Field.Root>\n </Box>\n <Field.Label>\n {formatMessage({\n id: 'content-releases.content-manager-edit-view.add-to-release.action-type-label',\n defaultMessage: 'What do you want to do with this entry?',\n })}\n </Field.Label>\n <ReleaseActionOpLine truncated
@@ -0,0 +1,104 @@
'use strict';
var jsxRuntime = require('react/jsx-runtime');
require('react');
var designSystem = require('@strapi/design-system');
var styledComponents = require('styled-components');
const getBorderLeftRadiusValue = (actionType)=>{
return actionType === 'publish' ? 1 : 0;
};
const getBorderRightRadiusValue = (actionType)=>{
return actionType === 'publish' ? 0 : 1;
};
const FieldWrapper = styledComponents.styled(designSystem.Field.Root)`
border-top-left-radius: ${({ $actionType, theme })=>theme.spaces[getBorderLeftRadiusValue($actionType)]};
border-bottom-left-radius: ${({ $actionType, theme })=>theme.spaces[getBorderLeftRadiusValue($actionType)]};
border-top-right-radius: ${({ $actionType, theme })=>theme.spaces[getBorderRightRadiusValue($actionType)]};
border-bottom-right-radius: ${({ $actionType, theme })=>theme.spaces[getBorderRightRadiusValue($actionType)]};
> label {
color: inherit;
padding: ${({ theme })=>`${theme.spaces[2]} ${theme.spaces[3]}`};
text-align: center;
vertical-align: middle;
text-transform: capitalize;
}
&[data-checked='true'] {
color: ${({ theme, $actionType })=>$actionType === 'publish' ? theme.colors.primary700 : theme.colors.danger600};
background-color: ${({ theme, $actionType })=>$actionType === 'publish' ? theme.colors.primary100 : theme.colors.danger100};
border-color: ${({ theme, $actionType })=>$actionType === 'publish' ? theme.colors.primary700 : theme.colors.danger600};
}
&[data-checked='false'] {
border-left: ${({ $actionType })=>$actionType === 'unpublish' && 'none'};
border-right: ${({ $actionType })=>$actionType === 'publish' && 'none'};
}
&[data-checked='false'][data-disabled='false']:hover {
color: ${({ theme })=>theme.colors.neutral700};
background-color: ${({ theme })=>theme.colors.neutral100};
border-color: ${({ theme })=>theme.colors.neutral200};
& > label {
cursor: pointer;
}
}
&[data-disabled='true'] {
color: ${({ theme })=>theme.colors.neutral600};
background-color: ${({ theme })=>theme.colors.neutral150};
border-color: ${({ theme })=>theme.colors.neutral300};
}
`;
const ActionOption = ({ selected, actionType, handleChange, name, disabled = false })=>{
return /*#__PURE__*/ jsxRuntime.jsx(FieldWrapper, {
$actionType: actionType,
background: "primary0",
borderColor: "neutral200",
color: selected === actionType ? 'primary600' : 'neutral600',
position: "relative",
cursor: "pointer",
"data-checked": selected === actionType,
"data-disabled": disabled && selected !== actionType,
children: /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Field.Label, {
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.VisuallyHidden, {
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Field.Input, {
type: "radio",
name: name,
checked: selected === actionType,
onChange: handleChange,
value: actionType,
disabled: disabled
})
}),
actionType
]
})
});
};
const ReleaseActionOptions = ({ selected, handleChange, name, disabled = false })=>{
return /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
children: [
/*#__PURE__*/ jsxRuntime.jsx(ActionOption, {
actionType: "publish",
selected: selected,
handleChange: handleChange,
name: name,
disabled: disabled
}),
/*#__PURE__*/ jsxRuntime.jsx(ActionOption, {
actionType: "unpublish",
selected: selected,
handleChange: handleChange,
name: name,
disabled: disabled
})
]
});
};
exports.ReleaseActionOptions = ReleaseActionOptions;
//# sourceMappingURL=ReleaseActionOptions.js.map
@@ -0,0 +1 @@
{"version":3,"file":"ReleaseActionOptions.js","sources":["../../../admin/src/components/ReleaseActionOptions.tsx"],"sourcesContent":["import * as React from 'react';\n\nimport { VisuallyHidden, Field, Flex } from '@strapi/design-system';\nimport { styled } from 'styled-components';\n\ninterface FieldWrapperProps extends Field.Props {\n actionType: 'publish' | 'unpublish';\n}\n\nconst getBorderLeftRadiusValue = (actionType: FieldWrapperProps['actionType']) => {\n return actionType === 'publish' ? 1 : 0;\n};\n\nconst getBorderRightRadiusValue = (actionType: FieldWrapperProps['actionType']) => {\n return actionType === 'publish' ? 0 : 1;\n};\n\nconst FieldWrapper = styled(Field.Root)<{\n $actionType: 'publish' | 'unpublish';\n}>`\n border-top-left-radius: ${({ $actionType, theme }) =>\n theme.spaces[getBorderLeftRadiusValue($actionType)]};\n border-bottom-left-radius: ${({ $actionType, theme }) =>\n theme.spaces[getBorderLeftRadiusValue($actionType)]};\n border-top-right-radius: ${({ $actionType, theme }) =>\n theme.spaces[getBorderRightRadiusValue($actionType)]};\n border-bottom-right-radius: ${({ $actionType, theme }) =>\n theme.spaces[getBorderRightRadiusValue($actionType)]};\n\n > label {\n color: inherit;\n padding: ${({ theme }) => `${theme.spaces[2]} ${theme.spaces[3]}`};\n text-align: center;\n vertical-align: middle;\n text-transform: capitalize;\n }\n\n &[data-checked='true'] {\n color: ${({ theme, $actionType }) =>\n $actionType === 'publish' ? theme.colors.primary700 : theme.colors.danger600};\n background-color: ${({ theme, $actionType }) =>\n $actionType === 'publish' ? theme.colors.primary100 : theme.colors.danger100};\n border-color: ${({ theme, $actionType }) =>\n $actionType === 'publish' ? theme.colors.primary700 : theme.colors.danger600};\n }\n\n &[data-checked='false'] {\n border-left: ${({ $actionType }) => $actionType === 'unpublish' && 'none'};\n border-right: ${({ $actionType }) => $actionType === 'publish' && 'none'};\n }\n\n &[data-checked='false'][data-disabled='false']:hover {\n color: ${({ theme }) => theme.colors.neutral700};\n background-color: ${({ theme }) => theme.colors.neutral100};\n border-color: ${({ theme }) => theme.colors.neutral200};\n\n & > label {\n cursor: pointer;\n }\n }\n\n &[data-disabled='true'] {\n color: ${({ theme }) => theme.colors.neutral600};\n background-color: ${({ theme }) => theme.colors.neutral150};\n border-color: ${({ theme }) => theme.colors.neutral300};\n }\n`;\n\ninterface ActionOptionProps {\n selected: 'publish' | 'unpublish';\n handleChange: (e: React.ChangeEvent<HTMLInputElement>) => void;\n name: string;\n disabled?: boolean;\n}\n\ninterface OptionProps extends ActionOptionProps {\n actionType: 'publish' | 'unpublish';\n}\n\nconst ActionOption = ({\n selected,\n actionType,\n handleChange,\n name,\n disabled = false,\n}: OptionProps) => {\n return (\n <FieldWrapper\n $actionType={actionType}\n background=\"primary0\"\n borderColor=\"neutral200\"\n color={selected === actionType ? 'primary600' : 'neutral600'}\n position=\"relative\"\n cursor=\"pointer\"\n data-checked={selected === actionType}\n data-disabled={disabled && selected !== actionType}\n >\n <Field.Label>\n <VisuallyHidden>\n <Field.Input\n type=\"radio\"\n name={name}\n checked={selected === actionType}\n onChange={handleChange}\n value={actionType}\n disabled={disabled}\n />\n </VisuallyHidden>\n {actionType}\n </Field.Label>\n </FieldWrapper>\n );\n};\n\nexport const ReleaseActionOptions = ({\n selected,\n handleChange,\n name,\n disabled = false,\n}: ActionOptionProps) => {\n return (\n <Flex>\n <ActionOption\n actionType=\"publish\"\n selected={selected}\n handleChange={handleChange}\n name={name}\n disabled={disabled}\n />\n <ActionOption\n actionType=\"unpublish\"\n selected={selected}\n handleChange={handleChange}\n name={name}\n disabled={disabled}\n />\n </Flex>\n );\n};\n"],"names":["getBorderLeftRadiusValue","actionType","getBorderRightRadiusValue","FieldWrapper","styled","Field","Root","$actionType","theme","spaces","colors","primary700","danger600","primary100","danger100","neutral700","neutral100","neutral200","neutral600","neutral150","neutral300","ActionOption","selected","handleChange","name","disabled","_jsx","background","borderColor","color","position","cursor","data-checked","data-disabled","_jsxs","Label","VisuallyHidden","Input","type","checked","onChange","value","ReleaseActionOptions","Flex"],"mappings":";;;;;;;AASA,MAAMA,2BAA2B,CAACC,UAAAA,GAAAA;IAChC,OAAOA,UAAAA,KAAe,YAAY,CAAI,GAAA,CAAA;AACxC,CAAA;AAEA,MAAMC,4BAA4B,CAACD,UAAAA,GAAAA;IACjC,OAAOA,UAAAA,KAAe,YAAY,CAAI,GAAA,CAAA;ALine truncated
@@ -0,0 +1,102 @@
import { jsxs, jsx } from 'react/jsx-runtime';
import 'react';
import { Field, Flex, VisuallyHidden } from '@strapi/design-system';
import { styled } from 'styled-components';
const getBorderLeftRadiusValue = (actionType)=>{
return actionType === 'publish' ? 1 : 0;
};
const getBorderRightRadiusValue = (actionType)=>{
return actionType === 'publish' ? 0 : 1;
};
const FieldWrapper = styled(Field.Root)`
border-top-left-radius: ${({ $actionType, theme })=>theme.spaces[getBorderLeftRadiusValue($actionType)]};
border-bottom-left-radius: ${({ $actionType, theme })=>theme.spaces[getBorderLeftRadiusValue($actionType)]};
border-top-right-radius: ${({ $actionType, theme })=>theme.spaces[getBorderRightRadiusValue($actionType)]};
border-bottom-right-radius: ${({ $actionType, theme })=>theme.spaces[getBorderRightRadiusValue($actionType)]};
> label {
color: inherit;
padding: ${({ theme })=>`${theme.spaces[2]} ${theme.spaces[3]}`};
text-align: center;
vertical-align: middle;
text-transform: capitalize;
}
&[data-checked='true'] {
color: ${({ theme, $actionType })=>$actionType === 'publish' ? theme.colors.primary700 : theme.colors.danger600};
background-color: ${({ theme, $actionType })=>$actionType === 'publish' ? theme.colors.primary100 : theme.colors.danger100};
border-color: ${({ theme, $actionType })=>$actionType === 'publish' ? theme.colors.primary700 : theme.colors.danger600};
}
&[data-checked='false'] {
border-left: ${({ $actionType })=>$actionType === 'unpublish' && 'none'};
border-right: ${({ $actionType })=>$actionType === 'publish' && 'none'};
}
&[data-checked='false'][data-disabled='false']:hover {
color: ${({ theme })=>theme.colors.neutral700};
background-color: ${({ theme })=>theme.colors.neutral100};
border-color: ${({ theme })=>theme.colors.neutral200};
& > label {
cursor: pointer;
}
}
&[data-disabled='true'] {
color: ${({ theme })=>theme.colors.neutral600};
background-color: ${({ theme })=>theme.colors.neutral150};
border-color: ${({ theme })=>theme.colors.neutral300};
}
`;
const ActionOption = ({ selected, actionType, handleChange, name, disabled = false })=>{
return /*#__PURE__*/ jsx(FieldWrapper, {
$actionType: actionType,
background: "primary0",
borderColor: "neutral200",
color: selected === actionType ? 'primary600' : 'neutral600',
position: "relative",
cursor: "pointer",
"data-checked": selected === actionType,
"data-disabled": disabled && selected !== actionType,
children: /*#__PURE__*/ jsxs(Field.Label, {
children: [
/*#__PURE__*/ jsx(VisuallyHidden, {
children: /*#__PURE__*/ jsx(Field.Input, {
type: "radio",
name: name,
checked: selected === actionType,
onChange: handleChange,
value: actionType,
disabled: disabled
})
}),
actionType
]
})
});
};
const ReleaseActionOptions = ({ selected, handleChange, name, disabled = false })=>{
return /*#__PURE__*/ jsxs(Flex, {
children: [
/*#__PURE__*/ jsx(ActionOption, {
actionType: "publish",
selected: selected,
handleChange: handleChange,
name: name,
disabled: disabled
}),
/*#__PURE__*/ jsx(ActionOption, {
actionType: "unpublish",
selected: selected,
handleChange: handleChange,
name: name,
disabled: disabled
})
]
});
};
export { ReleaseActionOptions };
//# sourceMappingURL=ReleaseActionOptions.mjs.map
@@ -0,0 +1 @@
{"version":3,"file":"ReleaseActionOptions.mjs","sources":["../../../admin/src/components/ReleaseActionOptions.tsx"],"sourcesContent":["import * as React from 'react';\n\nimport { VisuallyHidden, Field, Flex } from '@strapi/design-system';\nimport { styled } from 'styled-components';\n\ninterface FieldWrapperProps extends Field.Props {\n actionType: 'publish' | 'unpublish';\n}\n\nconst getBorderLeftRadiusValue = (actionType: FieldWrapperProps['actionType']) => {\n return actionType === 'publish' ? 1 : 0;\n};\n\nconst getBorderRightRadiusValue = (actionType: FieldWrapperProps['actionType']) => {\n return actionType === 'publish' ? 0 : 1;\n};\n\nconst FieldWrapper = styled(Field.Root)<{\n $actionType: 'publish' | 'unpublish';\n}>`\n border-top-left-radius: ${({ $actionType, theme }) =>\n theme.spaces[getBorderLeftRadiusValue($actionType)]};\n border-bottom-left-radius: ${({ $actionType, theme }) =>\n theme.spaces[getBorderLeftRadiusValue($actionType)]};\n border-top-right-radius: ${({ $actionType, theme }) =>\n theme.spaces[getBorderRightRadiusValue($actionType)]};\n border-bottom-right-radius: ${({ $actionType, theme }) =>\n theme.spaces[getBorderRightRadiusValue($actionType)]};\n\n > label {\n color: inherit;\n padding: ${({ theme }) => `${theme.spaces[2]} ${theme.spaces[3]}`};\n text-align: center;\n vertical-align: middle;\n text-transform: capitalize;\n }\n\n &[data-checked='true'] {\n color: ${({ theme, $actionType }) =>\n $actionType === 'publish' ? theme.colors.primary700 : theme.colors.danger600};\n background-color: ${({ theme, $actionType }) =>\n $actionType === 'publish' ? theme.colors.primary100 : theme.colors.danger100};\n border-color: ${({ theme, $actionType }) =>\n $actionType === 'publish' ? theme.colors.primary700 : theme.colors.danger600};\n }\n\n &[data-checked='false'] {\n border-left: ${({ $actionType }) => $actionType === 'unpublish' && 'none'};\n border-right: ${({ $actionType }) => $actionType === 'publish' && 'none'};\n }\n\n &[data-checked='false'][data-disabled='false']:hover {\n color: ${({ theme }) => theme.colors.neutral700};\n background-color: ${({ theme }) => theme.colors.neutral100};\n border-color: ${({ theme }) => theme.colors.neutral200};\n\n & > label {\n cursor: pointer;\n }\n }\n\n &[data-disabled='true'] {\n color: ${({ theme }) => theme.colors.neutral600};\n background-color: ${({ theme }) => theme.colors.neutral150};\n border-color: ${({ theme }) => theme.colors.neutral300};\n }\n`;\n\ninterface ActionOptionProps {\n selected: 'publish' | 'unpublish';\n handleChange: (e: React.ChangeEvent<HTMLInputElement>) => void;\n name: string;\n disabled?: boolean;\n}\n\ninterface OptionProps extends ActionOptionProps {\n actionType: 'publish' | 'unpublish';\n}\n\nconst ActionOption = ({\n selected,\n actionType,\n handleChange,\n name,\n disabled = false,\n}: OptionProps) => {\n return (\n <FieldWrapper\n $actionType={actionType}\n background=\"primary0\"\n borderColor=\"neutral200\"\n color={selected === actionType ? 'primary600' : 'neutral600'}\n position=\"relative\"\n cursor=\"pointer\"\n data-checked={selected === actionType}\n data-disabled={disabled && selected !== actionType}\n >\n <Field.Label>\n <VisuallyHidden>\n <Field.Input\n type=\"radio\"\n name={name}\n checked={selected === actionType}\n onChange={handleChange}\n value={actionType}\n disabled={disabled}\n />\n </VisuallyHidden>\n {actionType}\n </Field.Label>\n </FieldWrapper>\n );\n};\n\nexport const ReleaseActionOptions = ({\n selected,\n handleChange,\n name,\n disabled = false,\n}: ActionOptionProps) => {\n return (\n <Flex>\n <ActionOption\n actionType=\"publish\"\n selected={selected}\n handleChange={handleChange}\n name={name}\n disabled={disabled}\n />\n <ActionOption\n actionType=\"unpublish\"\n selected={selected}\n handleChange={handleChange}\n name={name}\n disabled={disabled}\n />\n </Flex>\n );\n};\n"],"names":["getBorderLeftRadiusValue","actionType","getBorderRightRadiusValue","FieldWrapper","styled","Field","Root","$actionType","theme","spaces","colors","primary700","danger600","primary100","danger100","neutral700","neutral100","neutral200","neutral600","neutral150","neutral300","ActionOption","selected","handleChange","name","disabled","_jsx","background","borderColor","color","position","cursor","data-checked","data-disabled","_jsxs","Label","VisuallyHidden","Input","type","checked","onChange","value","ReleaseActionOptions","Flex"],"mappings":";;;;;AASA,MAAMA,2BAA2B,CAACC,UAAAA,GAAAA;IAChC,OAAOA,UAAAA,KAAe,YAAY,CAAI,GAAA,CAAA;AACxC,CAAA;AAEA,MAAMC,4BAA4B,CAACD,UAAAA,GAAAA;IACjC,OAAOA,UAAAA,KAAe,YAAY,CAAI,GAAA,CAAA;AALine truncated
@@ -0,0 +1,103 @@
'use strict';
var jsxRuntime = require('react/jsx-runtime');
require('react');
var strapiAdmin = require('@strapi/admin/strapi-admin');
var designSystem = require('@strapi/design-system');
var icons = require('@strapi/icons');
var reactIntl = require('react-intl');
var release = require('../services/release.js');
const useReleasesList = (contentTypeUid, documentId)=>{
const listViewData = strapiAdmin.useTable('ListView', (state)=>state.rows);
const documentIds = listViewData.map((entry)=>entry.documentId);
const [{ query }] = strapiAdmin.useQueryParams();
const locale = query?.plugins?.i18n?.locale || undefined;
const response = release.useGetMappedEntriesInReleasesQuery({
contentTypeUid,
documentIds,
locale
}, {
skip: !documentIds || !contentTypeUid || documentIds.length === 0
});
const mappedEntriesInReleases = response.data || {};
return mappedEntriesInReleases?.[documentId] || [];
};
const addColumnToTableHook = ({ displayedHeaders, layout })=>{
const { options } = layout;
if (!options?.draftAndPublish) {
return {
displayedHeaders,
layout
};
}
return {
displayedHeaders: [
...displayedHeaders,
{
searchable: false,
sortable: false,
name: 'releases',
label: {
id: 'content-releases.content-manager.list-view.releases.header',
defaultMessage: 'To be released in'
},
cellFormatter: (props, _, { model })=>/*#__PURE__*/ jsxRuntime.jsx(ReleaseListCell, {
...props,
model: model
})
}
],
layout
};
};
const ReleaseListCell = ({ documentId, model })=>{
const releases = useReleasesList(model, documentId);
const { formatMessage } = reactIntl.useIntl();
return /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Popover.Root, {
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Popover.Trigger, {
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Button, {
variant: "ghost",
onClick: (e)=>e.stopPropagation(),
// TODO: find a way in the DS to define the widht and height of the icon
endIcon: releases.length > 0 ? /*#__PURE__*/ jsxRuntime.jsx(icons.CaretDown, {
width: "1.2rem",
height: "1.2rem"
}) : null,
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
style: {
maxWidth: '252px',
cursor: 'pointer'
},
textColor: "neutral800",
fontWeight: "regular",
children: releases.length > 0 ? formatMessage({
id: 'content-releases.content-manager.list-view.releases-number',
defaultMessage: '{number} {number, plural, one {release} other {releases}}'
}, {
number: releases.length
}) : '-'
})
})
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Popover.Content, {
children: /*#__PURE__*/ jsxRuntime.jsx("ul", {
children: releases.map(({ id, name })=>/*#__PURE__*/ jsxRuntime.jsx(designSystem.Box, {
padding: 3,
tag: "li",
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Link, {
href: `/admin/plugins/content-releases/${id}`,
isExternal: false,
children: name
})
}, id))
})
})
]
});
};
exports.ReleaseListCell = ReleaseListCell;
exports.addColumnToTableHook = addColumnToTableHook;
//# sourceMappingURL=ReleaseListCell.js.map
@@ -0,0 +1 @@
{"version":3,"file":"ReleaseListCell.js","sources":["../../../admin/src/components/ReleaseListCell.tsx"],"sourcesContent":["import * as React from 'react';\n\nimport { useTable, useQueryParams } from '@strapi/admin/strapi-admin';\nimport { ListFieldLayout, ListLayout } from '@strapi/content-manager/strapi-admin';\nimport { Box, Popover, Typography, Button, Link } from '@strapi/design-system';\nimport { CaretDown } from '@strapi/icons';\nimport { useIntl } from 'react-intl';\n\nimport { useGetMappedEntriesInReleasesQuery } from '../services/release';\n\nimport type { Modules, UID } from '@strapi/types';\n\n/* -------------------------------------------------------------------------------------------------\n * useReleasesList\n * -----------------------------------------------------------------------------------------------*/\ninterface QueryParams {\n plugins?: {\n i18n?: {\n locale: string;\n };\n };\n}\n\nconst useReleasesList = (contentTypeUid: UID.ContentType, documentId: Modules.Documents.ID) => {\n const listViewData = useTable('ListView', (state) => state.rows);\n const documentIds = listViewData.map((entry) => entry.documentId);\n const [{ query }] = useQueryParams();\n const locale = (query as QueryParams)?.plugins?.i18n?.locale || undefined;\n\n const response = useGetMappedEntriesInReleasesQuery(\n { contentTypeUid, documentIds, locale },\n { skip: !documentIds || !contentTypeUid || documentIds.length === 0 }\n );\n\n const mappedEntriesInReleases = response.data || {};\n\n return mappedEntriesInReleases?.[documentId] || [];\n};\n\n/* -------------------------------------------------------------------------------------------------\n * addColumnToTableHook\n * -----------------------------------------------------------------------------------------------*/\n\ninterface AddColumnToTableHookArgs {\n layout: ListLayout;\n displayedHeaders: ListFieldLayout[];\n}\n\nconst addColumnToTableHook = ({ displayedHeaders, layout }: AddColumnToTableHookArgs) => {\n const { options } = layout;\n\n if (!options?.draftAndPublish) {\n return { displayedHeaders, layout };\n }\n\n return {\n displayedHeaders: [\n ...displayedHeaders,\n {\n searchable: false,\n sortable: false,\n name: 'releases',\n label: {\n id: 'content-releases.content-manager.list-view.releases.header',\n defaultMessage: 'To be released in',\n },\n cellFormatter: (\n props: Modules.Documents.AnyDocument,\n _: any,\n { model }: { model: UID.ContentType }\n ) => <ReleaseListCell {...props} model={model} />,\n },\n ],\n layout,\n };\n};\n\n/* -------------------------------------------------------------------------------------------------\n * ReleaseListCell\n * -----------------------------------------------------------------------------------------------*/\n\ninterface ReleaseListCellProps extends Modules.Documents.AnyDocument {\n documentId: Modules.Documents.ID;\n model: UID.ContentType;\n}\n\nconst ReleaseListCell = ({ documentId, model }: ReleaseListCellProps) => {\n const releases = useReleasesList(model, documentId);\n const { formatMessage } = useIntl();\n\n return (\n <Popover.Root>\n <Popover.Trigger>\n <Button\n variant=\"ghost\"\n onClick={(e: React.MouseEvent<HTMLElement>) => e.stopPropagation()}\n // TODO: find a way in the DS to define the widht and height of the icon\n endIcon={releases.length > 0 ? <CaretDown width=\"1.2rem\" height=\"1.2rem\" /> : null}\n >\n <Typography\n style={{ maxWidth: '252px', cursor: 'pointer' }}\n textColor=\"neutral800\"\n fontWeight=\"regular\"\n >\n {releases.length > 0\n ? formatMessage(\n {\n id: 'content-releases.content-manager.list-view.releases-number',\n defaultMessage: '{number} {number, plural, one {release} other {releases}}',\n },\n {\n number: releases.length,\n }\n )\n : '-'}\n </Typography>\n </Button>\n </Popover.Trigger>\n <Popover.Content>\n <ul>\n {releases.map(({ id, name }) => (\n <Box key={id} padding={3} tag=\"li\">\n <Link href={`/admin/plugins/content-releases/${id}`} isExternal={false}>\n {name}\n </Link>\n </Box>\n ))}\n </ul>\n </Popover.Content>\n </Popover.Root>\n );\n};\n\nexport { ReleaseListCell, addColumnToTableHook };\nexport type { ReleaseListCellProps };\n"],"names":["useReleasesList","contentTypeUid","documentId","listViewData","useTable","state","rows","documentIds","map","entry","query","useQueryParams","locale","plugins","i18n","undefined","response","useGetMappedEntriesInReleasesQuery","skip","leLine truncated
@@ -0,0 +1,100 @@
import { jsx, jsxs } from 'react/jsx-runtime';
import 'react';
import { useTable, useQueryParams } from '@strapi/admin/strapi-admin';
import { Popover, Button, Typography, Box, Link } from '@strapi/design-system';
import { CaretDown } from '@strapi/icons';
import { useIntl } from 'react-intl';
import { useGetMappedEntriesInReleasesQuery } from '../services/release.mjs';
const useReleasesList = (contentTypeUid, documentId)=>{
const listViewData = useTable('ListView', (state)=>state.rows);
const documentIds = listViewData.map((entry)=>entry.documentId);
const [{ query }] = useQueryParams();
const locale = query?.plugins?.i18n?.locale || undefined;
const response = useGetMappedEntriesInReleasesQuery({
contentTypeUid,
documentIds,
locale
}, {
skip: !documentIds || !contentTypeUid || documentIds.length === 0
});
const mappedEntriesInReleases = response.data || {};
return mappedEntriesInReleases?.[documentId] || [];
};
const addColumnToTableHook = ({ displayedHeaders, layout })=>{
const { options } = layout;
if (!options?.draftAndPublish) {
return {
displayedHeaders,
layout
};
}
return {
displayedHeaders: [
...displayedHeaders,
{
searchable: false,
sortable: false,
name: 'releases',
label: {
id: 'content-releases.content-manager.list-view.releases.header',
defaultMessage: 'To be released in'
},
cellFormatter: (props, _, { model })=>/*#__PURE__*/ jsx(ReleaseListCell, {
...props,
model: model
})
}
],
layout
};
};
const ReleaseListCell = ({ documentId, model })=>{
const releases = useReleasesList(model, documentId);
const { formatMessage } = useIntl();
return /*#__PURE__*/ jsxs(Popover.Root, {
children: [
/*#__PURE__*/ jsx(Popover.Trigger, {
children: /*#__PURE__*/ jsx(Button, {
variant: "ghost",
onClick: (e)=>e.stopPropagation(),
// TODO: find a way in the DS to define the widht and height of the icon
endIcon: releases.length > 0 ? /*#__PURE__*/ jsx(CaretDown, {
width: "1.2rem",
height: "1.2rem"
}) : null,
children: /*#__PURE__*/ jsx(Typography, {
style: {
maxWidth: '252px',
cursor: 'pointer'
},
textColor: "neutral800",
fontWeight: "regular",
children: releases.length > 0 ? formatMessage({
id: 'content-releases.content-manager.list-view.releases-number',
defaultMessage: '{number} {number, plural, one {release} other {releases}}'
}, {
number: releases.length
}) : '-'
})
})
}),
/*#__PURE__*/ jsx(Popover.Content, {
children: /*#__PURE__*/ jsx("ul", {
children: releases.map(({ id, name })=>/*#__PURE__*/ jsx(Box, {
padding: 3,
tag: "li",
children: /*#__PURE__*/ jsx(Link, {
href: `/admin/plugins/content-releases/${id}`,
isExternal: false,
children: name
})
}, id))
})
})
]
});
};
export { ReleaseListCell, addColumnToTableHook };
//# sourceMappingURL=ReleaseListCell.mjs.map
@@ -0,0 +1 @@
{"version":3,"file":"ReleaseListCell.mjs","sources":["../../../admin/src/components/ReleaseListCell.tsx"],"sourcesContent":["import * as React from 'react';\n\nimport { useTable, useQueryParams } from '@strapi/admin/strapi-admin';\nimport { ListFieldLayout, ListLayout } from '@strapi/content-manager/strapi-admin';\nimport { Box, Popover, Typography, Button, Link } from '@strapi/design-system';\nimport { CaretDown } from '@strapi/icons';\nimport { useIntl } from 'react-intl';\n\nimport { useGetMappedEntriesInReleasesQuery } from '../services/release';\n\nimport type { Modules, UID } from '@strapi/types';\n\n/* -------------------------------------------------------------------------------------------------\n * useReleasesList\n * -----------------------------------------------------------------------------------------------*/\ninterface QueryParams {\n plugins?: {\n i18n?: {\n locale: string;\n };\n };\n}\n\nconst useReleasesList = (contentTypeUid: UID.ContentType, documentId: Modules.Documents.ID) => {\n const listViewData = useTable('ListView', (state) => state.rows);\n const documentIds = listViewData.map((entry) => entry.documentId);\n const [{ query }] = useQueryParams();\n const locale = (query as QueryParams)?.plugins?.i18n?.locale || undefined;\n\n const response = useGetMappedEntriesInReleasesQuery(\n { contentTypeUid, documentIds, locale },\n { skip: !documentIds || !contentTypeUid || documentIds.length === 0 }\n );\n\n const mappedEntriesInReleases = response.data || {};\n\n return mappedEntriesInReleases?.[documentId] || [];\n};\n\n/* -------------------------------------------------------------------------------------------------\n * addColumnToTableHook\n * -----------------------------------------------------------------------------------------------*/\n\ninterface AddColumnToTableHookArgs {\n layout: ListLayout;\n displayedHeaders: ListFieldLayout[];\n}\n\nconst addColumnToTableHook = ({ displayedHeaders, layout }: AddColumnToTableHookArgs) => {\n const { options } = layout;\n\n if (!options?.draftAndPublish) {\n return { displayedHeaders, layout };\n }\n\n return {\n displayedHeaders: [\n ...displayedHeaders,\n {\n searchable: false,\n sortable: false,\n name: 'releases',\n label: {\n id: 'content-releases.content-manager.list-view.releases.header',\n defaultMessage: 'To be released in',\n },\n cellFormatter: (\n props: Modules.Documents.AnyDocument,\n _: any,\n { model }: { model: UID.ContentType }\n ) => <ReleaseListCell {...props} model={model} />,\n },\n ],\n layout,\n };\n};\n\n/* -------------------------------------------------------------------------------------------------\n * ReleaseListCell\n * -----------------------------------------------------------------------------------------------*/\n\ninterface ReleaseListCellProps extends Modules.Documents.AnyDocument {\n documentId: Modules.Documents.ID;\n model: UID.ContentType;\n}\n\nconst ReleaseListCell = ({ documentId, model }: ReleaseListCellProps) => {\n const releases = useReleasesList(model, documentId);\n const { formatMessage } = useIntl();\n\n return (\n <Popover.Root>\n <Popover.Trigger>\n <Button\n variant=\"ghost\"\n onClick={(e: React.MouseEvent<HTMLElement>) => e.stopPropagation()}\n // TODO: find a way in the DS to define the widht and height of the icon\n endIcon={releases.length > 0 ? <CaretDown width=\"1.2rem\" height=\"1.2rem\" /> : null}\n >\n <Typography\n style={{ maxWidth: '252px', cursor: 'pointer' }}\n textColor=\"neutral800\"\n fontWeight=\"regular\"\n >\n {releases.length > 0\n ? formatMessage(\n {\n id: 'content-releases.content-manager.list-view.releases-number',\n defaultMessage: '{number} {number, plural, one {release} other {releases}}',\n },\n {\n number: releases.length,\n }\n )\n : '-'}\n </Typography>\n </Button>\n </Popover.Trigger>\n <Popover.Content>\n <ul>\n {releases.map(({ id, name }) => (\n <Box key={id} padding={3} tag=\"li\">\n <Link href={`/admin/plugins/content-releases/${id}`} isExternal={false}>\n {name}\n </Link>\n </Box>\n ))}\n </ul>\n </Popover.Content>\n </Popover.Root>\n );\n};\n\nexport { ReleaseListCell, addColumnToTableHook };\nexport type { ReleaseListCellProps };\n"],"names":["useReleasesList","contentTypeUid","documentId","listViewData","useTable","state","rows","documentIds","map","entry","query","useQueryParams","locale","plugins","i18n","undefined","response","useGetMappedEntriesInReleasesQuery","skip","lLine truncated
@@ -0,0 +1,323 @@
'use strict';
var jsxRuntime = require('react/jsx-runtime');
var React = require('react');
var designSystem = require('@strapi/design-system');
var dateFns = require('date-fns');
var dateFnsTz = require('date-fns-tz');
var formik = require('formik');
var reactIntl = require('react-intl');
var reactRouterDom = require('react-router-dom');
var pluginId = require('../pluginId.js');
var time = require('../utils/time.js');
var schemas = require('../validation/schemas.js');
function _interopNamespaceDefault(e) {
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n.default = e;
return Object.freeze(n);
}
var React__namespace = /*#__PURE__*/_interopNamespaceDefault(React);
const ReleaseModal = ({ handleClose, open, handleSubmit, initialValues, isLoading = false })=>{
const { formatMessage } = reactIntl.useIntl();
const { pathname } = reactRouterDom.useLocation();
const isCreatingRelease = pathname === `/plugins/${pluginId.pluginId}`;
// Set default first timezone from the list if no system timezone detected
const { timezoneList, systemTimezone = {
value: 'UTC+00:00-Africa/Abidjan '
} } = time.getTimezones(initialValues.scheduledAt ? new Date(initialValues.scheduledAt) : new Date());
/**
* Generate scheduled time using selected date, time and timezone
*/ const getScheduledTimestamp = (values)=>{
const { date, time, timezone } = values;
if (!date || !time || !timezone) return null;
const timezoneWithoutOffset = timezone.split('&')[1];
return dateFnsTz.zonedTimeToUtc(`${date} ${time}`, timezoneWithoutOffset);
};
/**
* Get timezone with offset to show the selected value in the dropdown
*/ const getTimezoneWithOffset = ()=>{
const currentTimezone = timezoneList.find((timezone)=>timezone.value.split('&')[1] === initialValues.timezone);
return currentTimezone?.value || systemTimezone.value;
};
return /*#__PURE__*/ jsxRuntime.jsx(designSystem.Modal.Root, {
open: open,
onOpenChange: handleClose,
children: /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Modal.Content, {
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Modal.Header, {
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Modal.Title, {
children: formatMessage({
id: 'content-releases.modal.title',
defaultMessage: '{isCreatingRelease, select, true {New release} other {Edit release}}'
}, {
isCreatingRelease: isCreatingRelease
})
})
}),
/*#__PURE__*/ jsxRuntime.jsx(formik.Formik, {
onSubmit: (values)=>{
handleSubmit({
...values,
timezone: values.timezone ? values.timezone.split('&')[1] : null,
scheduledAt: values.isScheduled ? getScheduledTimestamp(values) : null
});
},
initialValues: {
...initialValues,
timezone: initialValues.timezone ? getTimezoneWithOffset() : systemTimezone.value
},
validationSchema: schemas.RELEASE_SCHEMA,
validateOnChange: false,
children: ({ values, errors, handleChange, setFieldValue })=>{
return /*#__PURE__*/ jsxRuntime.jsxs(formik.Form, {
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Modal.Body, {
children: /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
direction: "column",
alignItems: "stretch",
gap: 6,
children: [
/*#__PURE__*/ jsxRuntime.jsxs(designSystem.Field.Root, {
name: "name",
error: errors.name && formatMessage({
id: errors.name,
defaultMessage: errors.name
}),
required: true,
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Field.Label, {
children: formatMessage({
id: 'content-releases.modal.form.input.label.release-name',
defaultMessage: 'Name'
})
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.TextInput, {
value: values.name,
onChange: handleChange
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Field.Error, {})
]
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Box, {
width: "max-content",
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Checkbox, {
name: "isScheduled",
checked: values.isScheduled,
onCheckedChange: (checked)=>{
setFieldValue('isScheduled', checked);
if (!checked) {
// Clear scheduling info from a release on unchecking schedule release, which reset scheduling info in DB
setFieldValue('date', null);
setFieldValue('time', '');
setFieldValue('timezone', null);
} else {
// On ticking back schedule release date, time and timezone should be restored to the initial state
setFieldValue('date', initialValues.date);
setFieldValue('time', initialValues.time);
setFieldValue('timezone', initialValues.timezone ?? systemTimezone?.value);
}
},
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
textColor: values.isScheduled ? 'primary600' : 'neutral800',
fontWeight: values.isScheduled ? 'semiBold' : 'regular',
children: formatMessage({
id: 'modal.form.input.label.schedule-release',
defaultMessage: 'Schedule release'
})
})
})
}),
values.isScheduled && /*#__PURE__*/ jsxRuntime.jsxs(jsxRuntime.Fragment, {
children: [
/*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
gap: 4,
alignItems: "start",
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Box, {
width: "100%",
children: /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Field.Root, {
name: "date",
error: errors.date && formatMessage({
id: errors.date,
defaultMessage: errors.date
}),
required: true,
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Field.Label, {
children: formatMessage({
id: 'content-releases.modal.form.input.label.date',
defaultMessage: 'Date'
})
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.DatePicker, {
onChange: (date)=>{
const isoFormatDate = date ? dateFns.formatISO(date, {
representation: 'date'
}) : null;
setFieldValue('date', isoFormatDate);
},
clearLabel: formatMessage({
id: 'content-releases.modal.form.input.clearLabel',
defaultMessage: 'Clear'
}),
onClear: ()=>{
setFieldValue('date', null);
},
value: values.date ? new Date(values.date) : new Date(),
minDate: dateFnsTz.utcToZonedTime(new Date(), values.timezone.split('&')[1])
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Field.Error, {})
]
})
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Box, {
width: "100%",
children: /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Field.Root, {
name: "time",
error: errors.time && formatMessage({
id: errors.time,
defaultMessage: errors.time
}),
required: true,
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Field.Label, {
children: formatMessage({
id: 'content-releases.modal.form.input.label.time',
defaultMessage: 'Time'
})
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.TimePicker, {
onChange: (time)=>{
setFieldValue('time', time);
},
clearLabel: formatMessage({
id: 'content-releases.modal.form.input.clearLabel',
defaultMessage: 'Clear'
}),
onClear: ()=>{
setFieldValue('time', '');
},
value: values.time || undefined
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Field.Error, {})
]
})
})
]
}),
/*#__PURE__*/ jsxRuntime.jsx(TimezoneComponent, {
timezoneOptions: timezoneList
})
]
})
]
})
}),
/*#__PURE__*/ jsxRuntime.jsxs(designSystem.Modal.Footer, {
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Modal.Close, {
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Button, {
variant: "tertiary",
name: "cancel",
children: formatMessage({
id: 'cancel',
defaultMessage: 'Cancel'
})
})
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Button, {
name: "submit",
loading: isLoading,
type: "submit",
children: formatMessage({
id: 'content-releases.modal.form.button.submit',
defaultMessage: '{isCreatingRelease, select, true {Continue} other {Save}}'
}, {
isCreatingRelease: isCreatingRelease
})
})
]
})
]
});
}
})
]
})
});
};
const TimezoneComponent = ({ timezoneOptions })=>{
const { values, errors, setFieldValue } = formik.useFormikContext();
const { formatMessage } = reactIntl.useIntl();
const [timezoneList, setTimezoneList] = React__namespace.useState(timezoneOptions);
React__namespace.useEffect(()=>{
if (values.date) {
// Update the timezone offset which varies with DST based on the date selected
const { timezoneList } = time.getTimezones(new Date(values.date));
setTimezoneList(timezoneList);
const updatedTimezone = values.timezone && timezoneList.find((tz)=>tz.value.split('&')[1] === values.timezone.split('&')[1]);
if (updatedTimezone) {
setFieldValue('timezone', updatedTimezone.value);
}
}
}, [
setFieldValue,
values.date,
values.timezone
]);
return /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Field.Root, {
name: "timezone",
error: errors.timezone && formatMessage({
id: errors.timezone,
defaultMessage: errors.timezone
}),
required: true,
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Field.Label, {
children: formatMessage({
id: 'content-releases.modal.form.input.label.timezone',
defaultMessage: 'Timezone'
})
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Combobox, {
autocomplete: {
type: 'list',
filter: 'contains'
},
value: values.timezone || undefined,
textValue: values.timezone ? values.timezone.replace(/&/, ' ') : undefined,
onChange: (timezone)=>{
setFieldValue('timezone', timezone);
},
onTextValueChange: (timezone)=>{
setFieldValue('timezone', timezone);
},
onClear: ()=>{
setFieldValue('timezone', '');
},
children: timezoneList.map((timezone)=>/*#__PURE__*/ jsxRuntime.jsx(designSystem.ComboboxOption, {
value: timezone.value,
children: timezone.value.replace(/&/, ' ')
}, timezone.value))
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Field.Error, {})
]
});
};
exports.ReleaseModal = ReleaseModal;
//# sourceMappingURL=ReleaseModal.js.map
@@ -0,0 +1 @@
{"version":3,"file":"ReleaseModal.js","sources":["../../../admin/src/components/ReleaseModal.tsx"],"sourcesContent":["import * as React from 'react';\n\nimport {\n Button,\n Modal,\n TextInput,\n Typography,\n Checkbox,\n Flex,\n Box,\n DatePicker,\n TimePicker,\n Combobox,\n ComboboxOption,\n Field,\n} from '@strapi/design-system';\nimport { formatISO } from 'date-fns';\nimport { utcToZonedTime, zonedTimeToUtc } from 'date-fns-tz';\nimport { Formik, Form, useFormikContext } from 'formik';\nimport { MessageDescriptor, useIntl } from 'react-intl';\nimport { useLocation } from 'react-router-dom';\n\nimport { pluginId } from '../pluginId';\nimport { getTimezones } from '../utils/time';\nimport { RELEASE_SCHEMA } from '../validation/schemas';\n\nexport interface FormValues {\n name: string;\n date?: string;\n time: string;\n timezone: string | null;\n isScheduled?: boolean;\n scheduledAt: Date | null;\n}\n\ninterface ReleaseModalProps {\n handleClose: () => void;\n handleSubmit: (values: FormValues) => void;\n isLoading?: boolean;\n initialValues: FormValues;\n open?: boolean;\n}\n\nexport const ReleaseModal = ({\n handleClose,\n open,\n handleSubmit,\n initialValues,\n isLoading = false,\n}: ReleaseModalProps) => {\n const { formatMessage } = useIntl();\n const { pathname } = useLocation();\n const isCreatingRelease = pathname === `/plugins/${pluginId}`;\n // Set default first timezone from the list if no system timezone detected\n const { timezoneList, systemTimezone = { value: 'UTC+00:00-Africa/Abidjan ' } } = getTimezones(\n initialValues.scheduledAt ? new Date(initialValues.scheduledAt) : new Date()\n );\n\n /**\n * Generate scheduled time using selected date, time and timezone\n */\n const getScheduledTimestamp = (values: FormValues) => {\n const { date, time, timezone } = values;\n if (!date || !time || !timezone) return null;\n const timezoneWithoutOffset = timezone.split('&')[1];\n return zonedTimeToUtc(`${date} ${time}`, timezoneWithoutOffset);\n };\n\n /**\n * Get timezone with offset to show the selected value in the dropdown\n */\n const getTimezoneWithOffset = () => {\n const currentTimezone = timezoneList.find(\n (timezone) => timezone.value.split('&')[1] === initialValues.timezone\n );\n return currentTimezone?.value || systemTimezone.value;\n };\n\n return (\n <Modal.Root open={open} onOpenChange={handleClose}>\n <Modal.Content>\n <Modal.Header>\n <Modal.Title>\n {formatMessage(\n {\n id: 'content-releases.modal.title',\n defaultMessage:\n '{isCreatingRelease, select, true {New release} other {Edit release}}',\n },\n { isCreatingRelease: isCreatingRelease }\n )}\n </Modal.Title>\n </Modal.Header>\n <Formik\n onSubmit={(values) => {\n handleSubmit({\n ...values,\n timezone: values.timezone ? values.timezone.split('&')[1] : null,\n scheduledAt: values.isScheduled ? getScheduledTimestamp(values) : null,\n });\n }}\n initialValues={{\n ...initialValues,\n timezone: initialValues.timezone ? getTimezoneWithOffset() : systemTimezone.value,\n }}\n validationSchema={RELEASE_SCHEMA}\n validateOnChange={false}\n >\n {({ values, errors, handleChange, setFieldValue }) => {\n return (\n <Form>\n <Modal.Body>\n <Flex direction=\"column\" alignItems=\"stretch\" gap={6}>\n <Field.Root\n name=\"name\"\n error={\n errors.name &&\n formatMessage({ id: errors.name, defaultMessage: errors.name })\n }\n required\n >\n <Field.Label>\n {formatMessage({\n id: 'content-releases.modal.form.input.label.release-name',\n defaultMessage: 'Name',\n })}\n </Field.Label>\n <TextInput value={values.name} onChange={handleChange} />\n <Field.Error />\n </Field.Root>\n <Box width=\"max-content\">\n <Checkbox\n name=\"isScheduled\"\n checked={values.isScheduled}\n onCheckedChange={(checked) => {\n setFieldValue('isScheduled', checked);\n if (!checked) {\n // Clear scheduling info from a release on unchecking schedule release, which reset scheduling info in DB\n setFieldValue('date', null);\n setFieldValuLine truncated
@@ -0,0 +1,302 @@
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
import * as React from 'react';
import { Modal, Flex, Field, TextInput, Box, Checkbox, Typography, DatePicker, TimePicker, Button, Combobox, ComboboxOption } from '@strapi/design-system';
import { formatISO } from 'date-fns';
import { utcToZonedTime, zonedTimeToUtc } from 'date-fns-tz';
import { Formik, Form, useFormikContext } from 'formik';
import { useIntl } from 'react-intl';
import { useLocation } from 'react-router-dom';
import { pluginId } from '../pluginId.mjs';
import { getTimezones } from '../utils/time.mjs';
import { RELEASE_SCHEMA } from '../validation/schemas.mjs';
const ReleaseModal = ({ handleClose, open, handleSubmit, initialValues, isLoading = false })=>{
const { formatMessage } = useIntl();
const { pathname } = useLocation();
const isCreatingRelease = pathname === `/plugins/${pluginId}`;
// Set default first timezone from the list if no system timezone detected
const { timezoneList, systemTimezone = {
value: 'UTC+00:00-Africa/Abidjan '
} } = getTimezones(initialValues.scheduledAt ? new Date(initialValues.scheduledAt) : new Date());
/**
* Generate scheduled time using selected date, time and timezone
*/ const getScheduledTimestamp = (values)=>{
const { date, time, timezone } = values;
if (!date || !time || !timezone) return null;
const timezoneWithoutOffset = timezone.split('&')[1];
return zonedTimeToUtc(`${date} ${time}`, timezoneWithoutOffset);
};
/**
* Get timezone with offset to show the selected value in the dropdown
*/ const getTimezoneWithOffset = ()=>{
const currentTimezone = timezoneList.find((timezone)=>timezone.value.split('&')[1] === initialValues.timezone);
return currentTimezone?.value || systemTimezone.value;
};
return /*#__PURE__*/ jsx(Modal.Root, {
open: open,
onOpenChange: handleClose,
children: /*#__PURE__*/ jsxs(Modal.Content, {
children: [
/*#__PURE__*/ jsx(Modal.Header, {
children: /*#__PURE__*/ jsx(Modal.Title, {
children: formatMessage({
id: 'content-releases.modal.title',
defaultMessage: '{isCreatingRelease, select, true {New release} other {Edit release}}'
}, {
isCreatingRelease: isCreatingRelease
})
})
}),
/*#__PURE__*/ jsx(Formik, {
onSubmit: (values)=>{
handleSubmit({
...values,
timezone: values.timezone ? values.timezone.split('&')[1] : null,
scheduledAt: values.isScheduled ? getScheduledTimestamp(values) : null
});
},
initialValues: {
...initialValues,
timezone: initialValues.timezone ? getTimezoneWithOffset() : systemTimezone.value
},
validationSchema: RELEASE_SCHEMA,
validateOnChange: false,
children: ({ values, errors, handleChange, setFieldValue })=>{
return /*#__PURE__*/ jsxs(Form, {
children: [
/*#__PURE__*/ jsx(Modal.Body, {
children: /*#__PURE__*/ jsxs(Flex, {
direction: "column",
alignItems: "stretch",
gap: 6,
children: [
/*#__PURE__*/ jsxs(Field.Root, {
name: "name",
error: errors.name && formatMessage({
id: errors.name,
defaultMessage: errors.name
}),
required: true,
children: [
/*#__PURE__*/ jsx(Field.Label, {
children: formatMessage({
id: 'content-releases.modal.form.input.label.release-name',
defaultMessage: 'Name'
})
}),
/*#__PURE__*/ jsx(TextInput, {
value: values.name,
onChange: handleChange
}),
/*#__PURE__*/ jsx(Field.Error, {})
]
}),
/*#__PURE__*/ jsx(Box, {
width: "max-content",
children: /*#__PURE__*/ jsx(Checkbox, {
name: "isScheduled",
checked: values.isScheduled,
onCheckedChange: (checked)=>{
setFieldValue('isScheduled', checked);
if (!checked) {
// Clear scheduling info from a release on unchecking schedule release, which reset scheduling info in DB
setFieldValue('date', null);
setFieldValue('time', '');
setFieldValue('timezone', null);
} else {
// On ticking back schedule release date, time and timezone should be restored to the initial state
setFieldValue('date', initialValues.date);
setFieldValue('time', initialValues.time);
setFieldValue('timezone', initialValues.timezone ?? systemTimezone?.value);
}
},
children: /*#__PURE__*/ jsx(Typography, {
textColor: values.isScheduled ? 'primary600' : 'neutral800',
fontWeight: values.isScheduled ? 'semiBold' : 'regular',
children: formatMessage({
id: 'modal.form.input.label.schedule-release',
defaultMessage: 'Schedule release'
})
})
})
}),
values.isScheduled && /*#__PURE__*/ jsxs(Fragment, {
children: [
/*#__PURE__*/ jsxs(Flex, {
gap: 4,
alignItems: "start",
children: [
/*#__PURE__*/ jsx(Box, {
width: "100%",
children: /*#__PURE__*/ jsxs(Field.Root, {
name: "date",
error: errors.date && formatMessage({
id: errors.date,
defaultMessage: errors.date
}),
required: true,
children: [
/*#__PURE__*/ jsx(Field.Label, {
children: formatMessage({
id: 'content-releases.modal.form.input.label.date',
defaultMessage: 'Date'
})
}),
/*#__PURE__*/ jsx(DatePicker, {
onChange: (date)=>{
const isoFormatDate = date ? formatISO(date, {
representation: 'date'
}) : null;
setFieldValue('date', isoFormatDate);
},
clearLabel: formatMessage({
id: 'content-releases.modal.form.input.clearLabel',
defaultMessage: 'Clear'
}),
onClear: ()=>{
setFieldValue('date', null);
},
value: values.date ? new Date(values.date) : new Date(),
minDate: utcToZonedTime(new Date(), values.timezone.split('&')[1])
}),
/*#__PURE__*/ jsx(Field.Error, {})
]
})
}),
/*#__PURE__*/ jsx(Box, {
width: "100%",
children: /*#__PURE__*/ jsxs(Field.Root, {
name: "time",
error: errors.time && formatMessage({
id: errors.time,
defaultMessage: errors.time
}),
required: true,
children: [
/*#__PURE__*/ jsx(Field.Label, {
children: formatMessage({
id: 'content-releases.modal.form.input.label.time',
defaultMessage: 'Time'
})
}),
/*#__PURE__*/ jsx(TimePicker, {
onChange: (time)=>{
setFieldValue('time', time);
},
clearLabel: formatMessage({
id: 'content-releases.modal.form.input.clearLabel',
defaultMessage: 'Clear'
}),
onClear: ()=>{
setFieldValue('time', '');
},
value: values.time || undefined
}),
/*#__PURE__*/ jsx(Field.Error, {})
]
})
})
]
}),
/*#__PURE__*/ jsx(TimezoneComponent, {
timezoneOptions: timezoneList
})
]
})
]
})
}),
/*#__PURE__*/ jsxs(Modal.Footer, {
children: [
/*#__PURE__*/ jsx(Modal.Close, {
children: /*#__PURE__*/ jsx(Button, {
variant: "tertiary",
name: "cancel",
children: formatMessage({
id: 'cancel',
defaultMessage: 'Cancel'
})
})
}),
/*#__PURE__*/ jsx(Button, {
name: "submit",
loading: isLoading,
type: "submit",
children: formatMessage({
id: 'content-releases.modal.form.button.submit',
defaultMessage: '{isCreatingRelease, select, true {Continue} other {Save}}'
}, {
isCreatingRelease: isCreatingRelease
})
})
]
})
]
});
}
})
]
})
});
};
const TimezoneComponent = ({ timezoneOptions })=>{
const { values, errors, setFieldValue } = useFormikContext();
const { formatMessage } = useIntl();
const [timezoneList, setTimezoneList] = React.useState(timezoneOptions);
React.useEffect(()=>{
if (values.date) {
// Update the timezone offset which varies with DST based on the date selected
const { timezoneList } = getTimezones(new Date(values.date));
setTimezoneList(timezoneList);
const updatedTimezone = values.timezone && timezoneList.find((tz)=>tz.value.split('&')[1] === values.timezone.split('&')[1]);
if (updatedTimezone) {
setFieldValue('timezone', updatedTimezone.value);
}
}
}, [
setFieldValue,
values.date,
values.timezone
]);
return /*#__PURE__*/ jsxs(Field.Root, {
name: "timezone",
error: errors.timezone && formatMessage({
id: errors.timezone,
defaultMessage: errors.timezone
}),
required: true,
children: [
/*#__PURE__*/ jsx(Field.Label, {
children: formatMessage({
id: 'content-releases.modal.form.input.label.timezone',
defaultMessage: 'Timezone'
})
}),
/*#__PURE__*/ jsx(Combobox, {
autocomplete: {
type: 'list',
filter: 'contains'
},
value: values.timezone || undefined,
textValue: values.timezone ? values.timezone.replace(/&/, ' ') : undefined,
onChange: (timezone)=>{
setFieldValue('timezone', timezone);
},
onTextValueChange: (timezone)=>{
setFieldValue('timezone', timezone);
},
onClear: ()=>{
setFieldValue('timezone', '');
},
children: timezoneList.map((timezone)=>/*#__PURE__*/ jsx(ComboboxOption, {
value: timezone.value,
children: timezone.value.replace(/&/, ' ')
}, timezone.value))
}),
/*#__PURE__*/ jsx(Field.Error, {})
]
});
};
export { ReleaseModal };
//# sourceMappingURL=ReleaseModal.mjs.map
@@ -0,0 +1 @@
{"version":3,"file":"ReleaseModal.mjs","sources":["../../../admin/src/components/ReleaseModal.tsx"],"sourcesContent":["import * as React from 'react';\n\nimport {\n Button,\n Modal,\n TextInput,\n Typography,\n Checkbox,\n Flex,\n Box,\n DatePicker,\n TimePicker,\n Combobox,\n ComboboxOption,\n Field,\n} from '@strapi/design-system';\nimport { formatISO } from 'date-fns';\nimport { utcToZonedTime, zonedTimeToUtc } from 'date-fns-tz';\nimport { Formik, Form, useFormikContext } from 'formik';\nimport { MessageDescriptor, useIntl } from 'react-intl';\nimport { useLocation } from 'react-router-dom';\n\nimport { pluginId } from '../pluginId';\nimport { getTimezones } from '../utils/time';\nimport { RELEASE_SCHEMA } from '../validation/schemas';\n\nexport interface FormValues {\n name: string;\n date?: string;\n time: string;\n timezone: string | null;\n isScheduled?: boolean;\n scheduledAt: Date | null;\n}\n\ninterface ReleaseModalProps {\n handleClose: () => void;\n handleSubmit: (values: FormValues) => void;\n isLoading?: boolean;\n initialValues: FormValues;\n open?: boolean;\n}\n\nexport const ReleaseModal = ({\n handleClose,\n open,\n handleSubmit,\n initialValues,\n isLoading = false,\n}: ReleaseModalProps) => {\n const { formatMessage } = useIntl();\n const { pathname } = useLocation();\n const isCreatingRelease = pathname === `/plugins/${pluginId}`;\n // Set default first timezone from the list if no system timezone detected\n const { timezoneList, systemTimezone = { value: 'UTC+00:00-Africa/Abidjan ' } } = getTimezones(\n initialValues.scheduledAt ? new Date(initialValues.scheduledAt) : new Date()\n );\n\n /**\n * Generate scheduled time using selected date, time and timezone\n */\n const getScheduledTimestamp = (values: FormValues) => {\n const { date, time, timezone } = values;\n if (!date || !time || !timezone) return null;\n const timezoneWithoutOffset = timezone.split('&')[1];\n return zonedTimeToUtc(`${date} ${time}`, timezoneWithoutOffset);\n };\n\n /**\n * Get timezone with offset to show the selected value in the dropdown\n */\n const getTimezoneWithOffset = () => {\n const currentTimezone = timezoneList.find(\n (timezone) => timezone.value.split('&')[1] === initialValues.timezone\n );\n return currentTimezone?.value || systemTimezone.value;\n };\n\n return (\n <Modal.Root open={open} onOpenChange={handleClose}>\n <Modal.Content>\n <Modal.Header>\n <Modal.Title>\n {formatMessage(\n {\n id: 'content-releases.modal.title',\n defaultMessage:\n '{isCreatingRelease, select, true {New release} other {Edit release}}',\n },\n { isCreatingRelease: isCreatingRelease }\n )}\n </Modal.Title>\n </Modal.Header>\n <Formik\n onSubmit={(values) => {\n handleSubmit({\n ...values,\n timezone: values.timezone ? values.timezone.split('&')[1] : null,\n scheduledAt: values.isScheduled ? getScheduledTimestamp(values) : null,\n });\n }}\n initialValues={{\n ...initialValues,\n timezone: initialValues.timezone ? getTimezoneWithOffset() : systemTimezone.value,\n }}\n validationSchema={RELEASE_SCHEMA}\n validateOnChange={false}\n >\n {({ values, errors, handleChange, setFieldValue }) => {\n return (\n <Form>\n <Modal.Body>\n <Flex direction=\"column\" alignItems=\"stretch\" gap={6}>\n <Field.Root\n name=\"name\"\n error={\n errors.name &&\n formatMessage({ id: errors.name, defaultMessage: errors.name })\n }\n required\n >\n <Field.Label>\n {formatMessage({\n id: 'content-releases.modal.form.input.label.release-name',\n defaultMessage: 'Name',\n })}\n </Field.Label>\n <TextInput value={values.name} onChange={handleChange} />\n <Field.Error />\n </Field.Root>\n <Box width=\"max-content\">\n <Checkbox\n name=\"isScheduled\"\n checked={values.isScheduled}\n onCheckedChange={(checked) => {\n setFieldValue('isScheduled', checked);\n if (!checked) {\n // Clear scheduling info from a release on unchecking schedule release, which reset scheduling info in DB\n setFieldValue('date', null);\n setFieldValLine truncated
@@ -0,0 +1,138 @@
'use strict';
var jsxRuntime = require('react/jsx-runtime');
var strapiAdmin = require('@strapi/admin/strapi-admin');
var strapiAdmin$1 = require('@strapi/content-manager/strapi-admin');
var designSystem = require('@strapi/design-system');
var reactIntl = require('react-intl');
var constants = require('../constants.js');
var release = require('../services/release.js');
var time = require('../utils/time.js');
var ReleaseActionMenu = require('./ReleaseActionMenu.js');
const Panel = ({ model, document, documentId, collectionType })=>{
const [{ query }] = strapiAdmin.useQueryParams();
const locale = query.plugins?.i18n?.locale;
const { edit: { options } } = strapiAdmin$1.unstable_useDocumentLayout(model);
const { formatMessage, formatDate, formatTime } = reactIntl.useIntl();
const { allowedActions } = strapiAdmin.useRBAC(constants.PERMISSIONS);
const { canRead, canDeleteAction } = allowedActions;
const response = release.useGetReleasesForEntryQuery({
contentType: model,
entryDocumentId: documentId,
locale,
hasEntryAttached: true
}, {
skip: !document
});
const releases = response.data?.data;
const getReleaseColorVariant = (actionType, shade)=>{
if (actionType === 'unpublish') {
return `secondary${shade}`;
}
return `success${shade}`;
};
// Project is not EE or contentType does not have draftAndPublish enabled
if (!window.strapi.isEE || !options?.draftAndPublish || !canRead) {
return null;
}
if (collectionType === 'collection-types' && (!documentId || documentId === 'create')) {
return null;
}
if (!releases || releases.length === 0) {
return null;
}
return {
title: formatMessage({
id: 'content-releases.plugin.name',
defaultMessage: 'Releases'
}),
content: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Flex, {
direction: "column",
alignItems: "stretch",
gap: 3,
width: "100%",
children: releases?.map((release)=>/*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
direction: "column",
alignItems: "start",
borderWidth: "1px",
borderStyle: "solid",
borderColor: getReleaseColorVariant(release.actions[0].type, '200'),
overflow: "hidden",
hasRadius: true,
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Box, {
paddingTop: 3,
paddingBottom: 3,
paddingLeft: 4,
paddingRight: 4,
background: getReleaseColorVariant(release.actions[0].type, '100'),
width: "100%",
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
fontSize: 1,
variant: "pi",
textColor: getReleaseColorVariant(release.actions[0].type, '600'),
children: formatMessage({
id: 'content-releases.content-manager-edit-view.list-releases.title',
defaultMessage: '{isPublish, select, true {Will be published in} other {Will be unpublished in}}'
}, {
isPublish: release.actions[0].type === 'publish'
})
})
}),
/*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
padding: 4,
direction: "column",
gap: 2,
width: "100%",
alignItems: "flex-start",
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
fontSize: 2,
fontWeight: "bold",
variant: "omega",
textColor: "neutral700",
children: release.name
}),
release.scheduledAt && release.timezone && /*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
variant: "pi",
textColor: "neutral600",
children: formatMessage({
id: 'content-releases.content-manager-edit-view.scheduled.date',
defaultMessage: '{date} at {time} ({offset})'
}, {
date: formatDate(new Date(release.scheduledAt), {
day: '2-digit',
month: '2-digit',
year: 'numeric',
timeZone: release.timezone
}),
time: formatTime(new Date(release.scheduledAt), {
hourCycle: 'h23',
timeZone: release.timezone
}),
offset: time.getTimezoneOffset(release.timezone, new Date(release.scheduledAt))
})
}),
canDeleteAction ? /*#__PURE__*/ jsxRuntime.jsxs(ReleaseActionMenu.ReleaseActionMenu.Root, {
hasTriggerBorder: true,
children: [
/*#__PURE__*/ jsxRuntime.jsx(ReleaseActionMenu.ReleaseActionMenu.EditReleaseItem, {
releaseId: release.id
}),
/*#__PURE__*/ jsxRuntime.jsx(ReleaseActionMenu.ReleaseActionMenu.DeleteReleaseActionItem, {
releaseId: release.id,
actionId: release.actions[0].id
})
]
}) : null
]
})
]
}, release.id))
})
};
};
exports.Panel = Panel;
//# sourceMappingURL=ReleasesPanel.js.map
@@ -0,0 +1 @@
{"version":3,"file":"ReleasesPanel.js","sources":["../../../admin/src/components/ReleasesPanel.tsx"],"sourcesContent":["import { useRBAC, useQueryParams } from '@strapi/admin/strapi-admin';\nimport { unstable_useDocumentLayout as useDocumentLayout } from '@strapi/content-manager/strapi-admin';\nimport { Box, Flex, Typography } from '@strapi/design-system';\nimport { useIntl } from 'react-intl';\n\nimport { PERMISSIONS } from '../constants';\nimport { useGetReleasesForEntryQuery } from '../services/release';\nimport { getTimezoneOffset } from '../utils/time';\n\nimport { ReleaseActionMenu } from './ReleaseActionMenu';\n\nimport type { PanelComponent, PanelComponentProps } from '@strapi/content-manager/strapi-admin';\n\nconst Panel: PanelComponent = ({\n model,\n document,\n documentId,\n collectionType,\n}: PanelComponentProps) => {\n const [{ query }] = useQueryParams<{ plugins: { i18n: { locale: string } } }>();\n const locale = query.plugins?.i18n?.locale;\n\n const {\n edit: { options },\n } = useDocumentLayout(model);\n const { formatMessage, formatDate, formatTime } = useIntl();\n\n const { allowedActions } = useRBAC(PERMISSIONS);\n const { canRead, canDeleteAction } = allowedActions;\n\n const response = useGetReleasesForEntryQuery(\n {\n contentType: model,\n entryDocumentId: documentId,\n locale,\n hasEntryAttached: true,\n },\n {\n skip: !document,\n }\n );\n const releases = response.data?.data;\n\n const getReleaseColorVariant = (\n actionType: 'publish' | 'unpublish',\n shade: '100' | '200' | '600'\n ) => {\n if (actionType === 'unpublish') {\n return `secondary${shade}`;\n }\n\n return `success${shade}`;\n };\n\n // Project is not EE or contentType does not have draftAndPublish enabled\n if (!window.strapi.isEE || !options?.draftAndPublish || !canRead) {\n return null;\n }\n\n if (collectionType === 'collection-types' && (!documentId || documentId === 'create')) {\n return null;\n }\n\n if (!releases || releases.length === 0) {\n return null;\n }\n\n return {\n title: formatMessage({\n id: 'content-releases.plugin.name',\n defaultMessage: 'Releases',\n }),\n content: (\n <Flex direction=\"column\" alignItems=\"stretch\" gap={3} width=\"100%\">\n {releases?.map((release) => (\n <Flex\n key={release.id}\n direction=\"column\"\n alignItems=\"start\"\n borderWidth=\"1px\"\n borderStyle=\"solid\"\n borderColor={getReleaseColorVariant(release.actions[0].type, '200')}\n overflow=\"hidden\"\n hasRadius\n >\n <Box\n paddingTop={3}\n paddingBottom={3}\n paddingLeft={4}\n paddingRight={4}\n background={getReleaseColorVariant(release.actions[0].type, '100')}\n width=\"100%\"\n >\n <Typography\n fontSize={1}\n variant=\"pi\"\n textColor={getReleaseColorVariant(release.actions[0].type, '600')}\n >\n {formatMessage(\n {\n id: 'content-releases.content-manager-edit-view.list-releases.title',\n defaultMessage:\n '{isPublish, select, true {Will be published in} other {Will be unpublished in}}',\n },\n { isPublish: release.actions[0].type === 'publish' }\n )}\n </Typography>\n </Box>\n <Flex padding={4} direction=\"column\" gap={2} width=\"100%\" alignItems=\"flex-start\">\n <Typography fontSize={2} fontWeight=\"bold\" variant=\"omega\" textColor=\"neutral700\">\n {release.name}\n </Typography>\n {release.scheduledAt && release.timezone && (\n <Typography variant=\"pi\" textColor=\"neutral600\">\n {formatMessage(\n {\n id: 'content-releases.content-manager-edit-view.scheduled.date',\n defaultMessage: '{date} at {time} ({offset})',\n },\n {\n date: formatDate(new Date(release.scheduledAt), {\n day: '2-digit',\n month: '2-digit',\n year: 'numeric',\n timeZone: release.timezone,\n }),\n time: formatTime(new Date(release.scheduledAt), {\n hourCycle: 'h23',\n timeZone: release.timezone,\n }),\n offset: getTimezoneOffset(release.timezone, new Date(release.scheduledAt)),\n }\n )}\n </Typography>\n )}\n {canDeleteAction ? (\n <ReleaseActionMenuLine truncated
@@ -0,0 +1,136 @@
import { jsx, jsxs } from 'react/jsx-runtime';
import { useQueryParams, useRBAC } from '@strapi/admin/strapi-admin';
import { unstable_useDocumentLayout } from '@strapi/content-manager/strapi-admin';
import { Flex, Box, Typography } from '@strapi/design-system';
import { useIntl } from 'react-intl';
import { PERMISSIONS } from '../constants.mjs';
import { useGetReleasesForEntryQuery } from '../services/release.mjs';
import { getTimezoneOffset } from '../utils/time.mjs';
import { ReleaseActionMenu } from './ReleaseActionMenu.mjs';
const Panel = ({ model, document, documentId, collectionType })=>{
const [{ query }] = useQueryParams();
const locale = query.plugins?.i18n?.locale;
const { edit: { options } } = unstable_useDocumentLayout(model);
const { formatMessage, formatDate, formatTime } = useIntl();
const { allowedActions } = useRBAC(PERMISSIONS);
const { canRead, canDeleteAction } = allowedActions;
const response = useGetReleasesForEntryQuery({
contentType: model,
entryDocumentId: documentId,
locale,
hasEntryAttached: true
}, {
skip: !document
});
const releases = response.data?.data;
const getReleaseColorVariant = (actionType, shade)=>{
if (actionType === 'unpublish') {
return `secondary${shade}`;
}
return `success${shade}`;
};
// Project is not EE or contentType does not have draftAndPublish enabled
if (!window.strapi.isEE || !options?.draftAndPublish || !canRead) {
return null;
}
if (collectionType === 'collection-types' && (!documentId || documentId === 'create')) {
return null;
}
if (!releases || releases.length === 0) {
return null;
}
return {
title: formatMessage({
id: 'content-releases.plugin.name',
defaultMessage: 'Releases'
}),
content: /*#__PURE__*/ jsx(Flex, {
direction: "column",
alignItems: "stretch",
gap: 3,
width: "100%",
children: releases?.map((release)=>/*#__PURE__*/ jsxs(Flex, {
direction: "column",
alignItems: "start",
borderWidth: "1px",
borderStyle: "solid",
borderColor: getReleaseColorVariant(release.actions[0].type, '200'),
overflow: "hidden",
hasRadius: true,
children: [
/*#__PURE__*/ jsx(Box, {
paddingTop: 3,
paddingBottom: 3,
paddingLeft: 4,
paddingRight: 4,
background: getReleaseColorVariant(release.actions[0].type, '100'),
width: "100%",
children: /*#__PURE__*/ jsx(Typography, {
fontSize: 1,
variant: "pi",
textColor: getReleaseColorVariant(release.actions[0].type, '600'),
children: formatMessage({
id: 'content-releases.content-manager-edit-view.list-releases.title',
defaultMessage: '{isPublish, select, true {Will be published in} other {Will be unpublished in}}'
}, {
isPublish: release.actions[0].type === 'publish'
})
})
}),
/*#__PURE__*/ jsxs(Flex, {
padding: 4,
direction: "column",
gap: 2,
width: "100%",
alignItems: "flex-start",
children: [
/*#__PURE__*/ jsx(Typography, {
fontSize: 2,
fontWeight: "bold",
variant: "omega",
textColor: "neutral700",
children: release.name
}),
release.scheduledAt && release.timezone && /*#__PURE__*/ jsx(Typography, {
variant: "pi",
textColor: "neutral600",
children: formatMessage({
id: 'content-releases.content-manager-edit-view.scheduled.date',
defaultMessage: '{date} at {time} ({offset})'
}, {
date: formatDate(new Date(release.scheduledAt), {
day: '2-digit',
month: '2-digit',
year: 'numeric',
timeZone: release.timezone
}),
time: formatTime(new Date(release.scheduledAt), {
hourCycle: 'h23',
timeZone: release.timezone
}),
offset: getTimezoneOffset(release.timezone, new Date(release.scheduledAt))
})
}),
canDeleteAction ? /*#__PURE__*/ jsxs(ReleaseActionMenu.Root, {
hasTriggerBorder: true,
children: [
/*#__PURE__*/ jsx(ReleaseActionMenu.EditReleaseItem, {
releaseId: release.id
}),
/*#__PURE__*/ jsx(ReleaseActionMenu.DeleteReleaseActionItem, {
releaseId: release.id,
actionId: release.actions[0].id
})
]
}) : null
]
})
]
}, release.id))
})
};
};
export { Panel };
//# sourceMappingURL=ReleasesPanel.mjs.map
@@ -0,0 +1 @@
{"version":3,"file":"ReleasesPanel.mjs","sources":["../../../admin/src/components/ReleasesPanel.tsx"],"sourcesContent":["import { useRBAC, useQueryParams } from '@strapi/admin/strapi-admin';\nimport { unstable_useDocumentLayout as useDocumentLayout } from '@strapi/content-manager/strapi-admin';\nimport { Box, Flex, Typography } from '@strapi/design-system';\nimport { useIntl } from 'react-intl';\n\nimport { PERMISSIONS } from '../constants';\nimport { useGetReleasesForEntryQuery } from '../services/release';\nimport { getTimezoneOffset } from '../utils/time';\n\nimport { ReleaseActionMenu } from './ReleaseActionMenu';\n\nimport type { PanelComponent, PanelComponentProps } from '@strapi/content-manager/strapi-admin';\n\nconst Panel: PanelComponent = ({\n model,\n document,\n documentId,\n collectionType,\n}: PanelComponentProps) => {\n const [{ query }] = useQueryParams<{ plugins: { i18n: { locale: string } } }>();\n const locale = query.plugins?.i18n?.locale;\n\n const {\n edit: { options },\n } = useDocumentLayout(model);\n const { formatMessage, formatDate, formatTime } = useIntl();\n\n const { allowedActions } = useRBAC(PERMISSIONS);\n const { canRead, canDeleteAction } = allowedActions;\n\n const response = useGetReleasesForEntryQuery(\n {\n contentType: model,\n entryDocumentId: documentId,\n locale,\n hasEntryAttached: true,\n },\n {\n skip: !document,\n }\n );\n const releases = response.data?.data;\n\n const getReleaseColorVariant = (\n actionType: 'publish' | 'unpublish',\n shade: '100' | '200' | '600'\n ) => {\n if (actionType === 'unpublish') {\n return `secondary${shade}`;\n }\n\n return `success${shade}`;\n };\n\n // Project is not EE or contentType does not have draftAndPublish enabled\n if (!window.strapi.isEE || !options?.draftAndPublish || !canRead) {\n return null;\n }\n\n if (collectionType === 'collection-types' && (!documentId || documentId === 'create')) {\n return null;\n }\n\n if (!releases || releases.length === 0) {\n return null;\n }\n\n return {\n title: formatMessage({\n id: 'content-releases.plugin.name',\n defaultMessage: 'Releases',\n }),\n content: (\n <Flex direction=\"column\" alignItems=\"stretch\" gap={3} width=\"100%\">\n {releases?.map((release) => (\n <Flex\n key={release.id}\n direction=\"column\"\n alignItems=\"start\"\n borderWidth=\"1px\"\n borderStyle=\"solid\"\n borderColor={getReleaseColorVariant(release.actions[0].type, '200')}\n overflow=\"hidden\"\n hasRadius\n >\n <Box\n paddingTop={3}\n paddingBottom={3}\n paddingLeft={4}\n paddingRight={4}\n background={getReleaseColorVariant(release.actions[0].type, '100')}\n width=\"100%\"\n >\n <Typography\n fontSize={1}\n variant=\"pi\"\n textColor={getReleaseColorVariant(release.actions[0].type, '600')}\n >\n {formatMessage(\n {\n id: 'content-releases.content-manager-edit-view.list-releases.title',\n defaultMessage:\n '{isPublish, select, true {Will be published in} other {Will be unpublished in}}',\n },\n { isPublish: release.actions[0].type === 'publish' }\n )}\n </Typography>\n </Box>\n <Flex padding={4} direction=\"column\" gap={2} width=\"100%\" alignItems=\"flex-start\">\n <Typography fontSize={2} fontWeight=\"bold\" variant=\"omega\" textColor=\"neutral700\">\n {release.name}\n </Typography>\n {release.scheduledAt && release.timezone && (\n <Typography variant=\"pi\" textColor=\"neutral600\">\n {formatMessage(\n {\n id: 'content-releases.content-manager-edit-view.scheduled.date',\n defaultMessage: '{date} at {time} ({offset})',\n },\n {\n date: formatDate(new Date(release.scheduledAt), {\n day: '2-digit',\n month: '2-digit',\n year: 'numeric',\n timeZone: release.timezone,\n }),\n time: formatTime(new Date(release.scheduledAt), {\n hourCycle: 'h23',\n timeZone: release.timezone,\n }),\n offset: getTimezoneOffset(release.timezone, new Date(release.scheduledAt)),\n }\n )}\n </Typography>\n )}\n {canDeleteAction ? (\n <ReleaseActionMenLine truncated
+77
View File
@@ -0,0 +1,77 @@
'use strict';
const PERMISSIONS = {
main: [
{
action: 'plugin::content-releases.read',
subject: null,
id: '',
actionParameters: {},
properties: {},
conditions: []
}
],
create: [
{
action: 'plugin::content-releases.create',
subject: null,
id: '',
actionParameters: {},
properties: {},
conditions: []
}
],
update: [
{
action: 'plugin::content-releases.update',
subject: null,
id: '',
actionParameters: {},
properties: {},
conditions: []
}
],
delete: [
{
action: 'plugin::content-releases.delete',
subject: null,
id: '',
actionParameters: {},
properties: {},
conditions: []
}
],
createAction: [
{
action: 'plugin::content-releases.create-action',
subject: null,
id: '',
actionParameters: {},
properties: {},
conditions: []
}
],
deleteAction: [
{
action: 'plugin::content-releases.delete-action',
subject: null,
id: '',
actionParameters: {},
properties: {},
conditions: []
}
],
publish: [
{
action: 'plugin::content-releases.publish',
subject: null,
id: '',
actionParameters: {},
properties: {},
conditions: []
}
]
};
exports.PERMISSIONS = PERMISSIONS;
//# sourceMappingURL=constants.js.map
@@ -0,0 +1 @@
{"version":3,"file":"constants.js","sources":["../../admin/src/constants.ts"],"sourcesContent":["import type { Permission as StrapiPermission } from '@strapi/admin/strapi-admin';\n\nexport const PERMISSIONS = {\n main: [\n {\n action: 'plugin::content-releases.read',\n subject: null,\n id: '',\n actionParameters: {},\n properties: {},\n conditions: [],\n },\n ],\n create: [\n {\n action: 'plugin::content-releases.create',\n subject: null,\n id: '',\n actionParameters: {},\n properties: {},\n conditions: [],\n },\n ],\n update: [\n {\n action: 'plugin::content-releases.update',\n subject: null,\n id: '',\n actionParameters: {},\n properties: {},\n conditions: [],\n },\n ],\n delete: [\n {\n action: 'plugin::content-releases.delete',\n subject: null,\n id: '',\n actionParameters: {},\n properties: {},\n conditions: [],\n },\n ],\n createAction: [\n {\n action: 'plugin::content-releases.create-action',\n subject: null,\n id: '',\n actionParameters: {},\n properties: {},\n conditions: [],\n },\n ],\n deleteAction: [\n {\n action: 'plugin::content-releases.delete-action',\n subject: null,\n id: '',\n actionParameters: {},\n properties: {},\n conditions: [],\n },\n ],\n publish: [\n {\n action: 'plugin::content-releases.publish',\n subject: null,\n id: '',\n actionParameters: {},\n properties: {},\n conditions: [],\n },\n ],\n} satisfies Record<string, StrapiPermission[]>;\n\nexport const PERMISSIONS_SETTINGS = {\n read: [\n {\n action: 'plugin::content-releases.settings.read',\n subject: null,\n id: '',\n actionParameters: {},\n properties: {},\n conditions: [],\n },\n ],\n update: [\n {\n action: 'plugin::content-releases.settings.update',\n subject: null,\n id: '',\n actionParameters: {},\n properties: {},\n conditions: [],\n },\n ],\n} satisfies Record<string, StrapiPermission[]>;\n"],"names":["PERMISSIONS","main","action","subject","id","actionParameters","properties","conditions","create","update","delete","createAction","deleteAction","publish"],"mappings":";;MAEaA,WAAc,GAAA;IACzBC,IAAM,EAAA;AACJ,QAAA;YACEC,MAAQ,EAAA,+BAAA;YACRC,OAAS,EAAA,IAAA;YACTC,EAAI,EAAA,EAAA;AACJC,YAAAA,gBAAAA,EAAkB,EAAC;AACnBC,YAAAA,UAAAA,EAAY,EAAC;AACbC,YAAAA,UAAAA,EAAY;AACd;AACD,KAAA;IACDC,MAAQ,EAAA;AACN,QAAA;YACEN,MAAQ,EAAA,iCAAA;YACRC,OAAS,EAAA,IAAA;YACTC,EAAI,EAAA,EAAA;AACJC,YAAAA,gBAAAA,EAAkB,EAAC;AACnBC,YAAAA,UAAAA,EAAY,EAAC;AACbC,YAAAA,UAAAA,EAAY;AACd;AACD,KAAA;IACDE,MAAQ,EAAA;AACN,QAAA;YACEP,MAAQ,EAAA,iCAAA;YACRC,OAAS,EAAA,IAAA;YACTC,EAAI,EAAA,EAAA;AACJC,YAAAA,gBAAAA,EAAkB,EAAC;AACnBC,YAAAA,UAAAA,EAAY,EAAC;AACbC,YAAAA,UAAAA,EAAY;AACd;AACD,KAAA;IACDG,MAAQ,EAAA;AACN,QAAA;YACER,MAAQ,EAAA,iCAAA;YACRC,OAAS,EAAA,IAAA;YACTC,EAAI,EAAA,EAAA;AACJC,YAAAA,gBAAAA,EAAkB,EAAC;AACnBC,YAAAA,UAAAA,EAAY,EAAC;AACbC,YAAAA,UAAAA,EAAY;AACd;AACD,KAAA;IACDI,YAAc,EAAA;AACZ,QAAA;YACET,MAAQ,EAAA,wCAAA;YACRC,OAAS,EAAA,IAAA;YACTC,EAAI,EAAA,EAAA;AACJC,YAAAA,gBAAAA,EAAkB,EAAC;AACnBC,YAAAA,UAAAA,EAAY,EAAC;AACbC,YAAAA,UAAAA,EAAY;AACd;AACD,KAAA;IACDK,YAAc,EAAA;AACZ,QAAA;YACEV,MAAQ,EAAA,wCAAA;YACRC,OAAS,EAAA,IAAA;YACTC,EAAI,EAAA,EAAA;AACJC,YAAAA,gBAAAA,EAAkB,EAAC;AACnBC,YAAAA,UAAAA,EAAY,EAAC;AACbC,YAAAA,UAAAA,EAAY;AACd;AACD,KAAA;IACDM,OAAS,EAAA;AACP,QAAA;YACEX,MAAQ,EAAA,kCAAA;YACRC,OAAS,EAAA,IAAA;YACTC,EAAI,EAAA,EAAA;AACJC,YAAAA,gBAAAA,EAAkB,EAAC;AACnBC,YAAAA,UAAAA,EAAY,EAAC;AACbC,YAAAA,UAAAA,EAAY;AACd;AACD;AACH;;;;"}
+75
View File
@@ -0,0 +1,75 @@
const PERMISSIONS = {
main: [
{
action: 'plugin::content-releases.read',
subject: null,
id: '',
actionParameters: {},
properties: {},
conditions: []
}
],
create: [
{
action: 'plugin::content-releases.create',
subject: null,
id: '',
actionParameters: {},
properties: {},
conditions: []
}
],
update: [
{
action: 'plugin::content-releases.update',
subject: null,
id: '',
actionParameters: {},
properties: {},
conditions: []
}
],
delete: [
{
action: 'plugin::content-releases.delete',
subject: null,
id: '',
actionParameters: {},
properties: {},
conditions: []
}
],
createAction: [
{
action: 'plugin::content-releases.create-action',
subject: null,
id: '',
actionParameters: {},
properties: {},
conditions: []
}
],
deleteAction: [
{
action: 'plugin::content-releases.delete-action',
subject: null,
id: '',
actionParameters: {},
properties: {},
conditions: []
}
],
publish: [
{
action: 'plugin::content-releases.publish',
subject: null,
id: '',
actionParameters: {},
properties: {},
conditions: []
}
]
};
export { PERMISSIONS };
//# sourceMappingURL=constants.mjs.map
@@ -0,0 +1 @@
{"version":3,"file":"constants.mjs","sources":["../../admin/src/constants.ts"],"sourcesContent":["import type { Permission as StrapiPermission } from '@strapi/admin/strapi-admin';\n\nexport const PERMISSIONS = {\n main: [\n {\n action: 'plugin::content-releases.read',\n subject: null,\n id: '',\n actionParameters: {},\n properties: {},\n conditions: [],\n },\n ],\n create: [\n {\n action: 'plugin::content-releases.create',\n subject: null,\n id: '',\n actionParameters: {},\n properties: {},\n conditions: [],\n },\n ],\n update: [\n {\n action: 'plugin::content-releases.update',\n subject: null,\n id: '',\n actionParameters: {},\n properties: {},\n conditions: [],\n },\n ],\n delete: [\n {\n action: 'plugin::content-releases.delete',\n subject: null,\n id: '',\n actionParameters: {},\n properties: {},\n conditions: [],\n },\n ],\n createAction: [\n {\n action: 'plugin::content-releases.create-action',\n subject: null,\n id: '',\n actionParameters: {},\n properties: {},\n conditions: [],\n },\n ],\n deleteAction: [\n {\n action: 'plugin::content-releases.delete-action',\n subject: null,\n id: '',\n actionParameters: {},\n properties: {},\n conditions: [],\n },\n ],\n publish: [\n {\n action: 'plugin::content-releases.publish',\n subject: null,\n id: '',\n actionParameters: {},\n properties: {},\n conditions: [],\n },\n ],\n} satisfies Record<string, StrapiPermission[]>;\n\nexport const PERMISSIONS_SETTINGS = {\n read: [\n {\n action: 'plugin::content-releases.settings.read',\n subject: null,\n id: '',\n actionParameters: {},\n properties: {},\n conditions: [],\n },\n ],\n update: [\n {\n action: 'plugin::content-releases.settings.update',\n subject: null,\n id: '',\n actionParameters: {},\n properties: {},\n conditions: [],\n },\n ],\n} satisfies Record<string, StrapiPermission[]>;\n"],"names":["PERMISSIONS","main","action","subject","id","actionParameters","properties","conditions","create","update","delete","createAction","deleteAction","publish"],"mappings":"MAEaA,WAAc,GAAA;IACzBC,IAAM,EAAA;AACJ,QAAA;YACEC,MAAQ,EAAA,+BAAA;YACRC,OAAS,EAAA,IAAA;YACTC,EAAI,EAAA,EAAA;AACJC,YAAAA,gBAAAA,EAAkB,EAAC;AACnBC,YAAAA,UAAAA,EAAY,EAAC;AACbC,YAAAA,UAAAA,EAAY;AACd;AACD,KAAA;IACDC,MAAQ,EAAA;AACN,QAAA;YACEN,MAAQ,EAAA,iCAAA;YACRC,OAAS,EAAA,IAAA;YACTC,EAAI,EAAA,EAAA;AACJC,YAAAA,gBAAAA,EAAkB,EAAC;AACnBC,YAAAA,UAAAA,EAAY,EAAC;AACbC,YAAAA,UAAAA,EAAY;AACd;AACD,KAAA;IACDE,MAAQ,EAAA;AACN,QAAA;YACEP,MAAQ,EAAA,iCAAA;YACRC,OAAS,EAAA,IAAA;YACTC,EAAI,EAAA,EAAA;AACJC,YAAAA,gBAAAA,EAAkB,EAAC;AACnBC,YAAAA,UAAAA,EAAY,EAAC;AACbC,YAAAA,UAAAA,EAAY;AACd;AACD,KAAA;IACDG,MAAQ,EAAA;AACN,QAAA;YACER,MAAQ,EAAA,iCAAA;YACRC,OAAS,EAAA,IAAA;YACTC,EAAI,EAAA,EAAA;AACJC,YAAAA,gBAAAA,EAAkB,EAAC;AACnBC,YAAAA,UAAAA,EAAY,EAAC;AACbC,YAAAA,UAAAA,EAAY;AACd;AACD,KAAA;IACDI,YAAc,EAAA;AACZ,QAAA;YACET,MAAQ,EAAA,wCAAA;YACRC,OAAS,EAAA,IAAA;YACTC,EAAI,EAAA,EAAA;AACJC,YAAAA,gBAAAA,EAAkB,EAAC;AACnBC,YAAAA,UAAAA,EAAY,EAAC;AACbC,YAAAA,UAAAA,EAAY;AACd;AACD,KAAA;IACDK,YAAc,EAAA;AACZ,QAAA;YACEV,MAAQ,EAAA,wCAAA;YACRC,OAAS,EAAA,IAAA;YACTC,EAAI,EAAA,EAAA;AACJC,YAAAA,gBAAAA,EAAkB,EAAC;AACnBC,YAAAA,UAAAA,EAAY,EAAC;AACbC,YAAAA,UAAAA,EAAY;AACd;AACD,KAAA;IACDM,OAAS,EAAA;AACP,QAAA;YACEX,MAAQ,EAAA,kCAAA;YACRC,OAAS,EAAA,IAAA;YACTC,EAAI,EAAA,EAAA;AACJC,YAAAA,gBAAAA,EAAkB,EAAC;AACnBC,YAAAA,UAAAA,EAAY,EAAC;AACbC,YAAAA,UAAAA,EAAY;AACd;AACD;AACH;;;;"}
+126
View File
@@ -0,0 +1,126 @@
'use strict';
var icons = require('@strapi/icons');
var ReleaseAction = require('./components/ReleaseAction.js');
var ReleaseActionModal = require('./components/ReleaseActionModal.js');
var ReleaseListCell = require('./components/ReleaseListCell.js');
var ReleasesPanel = require('./components/ReleasesPanel.js');
var constants = require('./constants.js');
var pluginId = require('./pluginId.js');
var prefixPluginTranslations = require('./utils/prefixPluginTranslations.js');
function _interopNamespaceDefaultOnly (e) { return Object.freeze({ __proto__: null, default: e }); }
function __variableDynamicImportRuntime3__(path) {
switch (path) {
case './translations/en.json': return Promise.resolve().then(function () { return /*#__PURE__*/_interopNamespaceDefaultOnly(require('./translations/en.json.js')); });
case './translations/uk.json': return Promise.resolve().then(function () { return /*#__PURE__*/_interopNamespaceDefaultOnly(require('./translations/uk.json.js')); });
default: return new Promise(function(resolve, reject) {
(typeof queueMicrotask === 'function' ? queueMicrotask : setTimeout)(
reject.bind(null, new Error("Unknown variable dynamic import: " + path))
);
})
}
}
// eslint-disable-next-line import/no-default-export
const admin = {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
register (app) {
/**
* Hook that adds the locale column in the Release Details table
* @constant
* @type {string}
*/ app.createHook('ContentReleases/pages/ReleaseDetails/add-locale-in-releases');
if (window.strapi.features.isEnabled('cms-content-releases')) {
app.addMenuLink({
to: `plugins/${pluginId.pluginId}`,
icon: icons.PaperPlane,
intlLabel: {
id: `${pluginId.pluginId}.plugin.name`,
defaultMessage: 'Releases'
},
Component: ()=>Promise.resolve().then(function () { return require('./pages/App.js'); }).then((mod)=>({
default: mod.App
})),
permissions: constants.PERMISSIONS.main,
position: 2
});
// Insert the releases container into the CM's sidebar on the Edit View
const contentManagerPluginApis = app.getPlugin('content-manager').apis;
if ('addEditViewSidePanel' in contentManagerPluginApis && typeof contentManagerPluginApis.addEditViewSidePanel === 'function') {
contentManagerPluginApis.addEditViewSidePanel([
ReleasesPanel.Panel
]);
}
// Insert the "add to release" action into the CM's Edit View
if ('addDocumentAction' in contentManagerPluginApis && typeof contentManagerPluginApis.addDocumentAction === 'function') {
contentManagerPluginApis.addDocumentAction((actions)=>{
const indexOfDeleteAction = actions.findIndex((action)=>action.type === 'unpublish');
actions.splice(indexOfDeleteAction, 0, ReleaseActionModal.ReleaseActionModalForm);
return actions;
});
}
app.addSettingsLink('global', {
id: pluginId.pluginId,
to: 'releases',
intlLabel: {
id: `${pluginId.pluginId}.plugin.name`,
defaultMessage: 'Releases'
},
permissions: [],
async Component () {
const { ProtectedReleasesSettingsPage } = await Promise.resolve().then(function () { return require('./pages/ReleasesSettingsPage.js'); });
return {
default: ProtectedReleasesSettingsPage
};
}
});
if ('addBulkAction' in contentManagerPluginApis && typeof contentManagerPluginApis.addBulkAction === 'function') {
contentManagerPluginApis.addBulkAction((actions)=>{
// We want to add this action to just before the delete action all the time
const deleteActionIndex = actions.findIndex((action)=>action.type === 'delete');
actions.splice(deleteActionIndex, 0, ReleaseAction.ReleaseAction);
return actions;
});
}
// Hook that adds a column into the CM's LV table
app.registerHook('Admin/CM/pages/ListView/inject-column-in-table', ReleaseListCell.addColumnToTableHook);
} else if (!window.strapi.features.isEnabled('cms-content-releases') && window.strapi?.flags?.promoteEE) {
app.addSettingsLink('global', {
id: pluginId.pluginId,
to: '/plugins/purchase-content-releases',
intlLabel: {
id: `${pluginId.pluginId}.plugin.name`,
defaultMessage: 'Releases'
},
permissions: [],
async Component () {
const { PurchaseContentReleases } = await Promise.resolve().then(function () { return require('./pages/PurchaseContentReleases.js'); });
return {
default: PurchaseContentReleases
};
},
licenseOnly: true
});
}
},
async registerTrads ({ locales }) {
const importedTrads = await Promise.all(locales.map((locale)=>{
return __variableDynamicImportRuntime3__(`./translations/${locale}.json`).then(({ default: data })=>{
return {
data: prefixPluginTranslations.prefixPluginTranslations(data, 'content-releases'),
locale
};
}).catch(()=>{
return {
data: {},
locale
};
});
}));
return Promise.resolve(importedTrads);
}
};
module.exports = admin;
//# sourceMappingURL=index.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"index.js","sources":["../../admin/src/index.ts"],"sourcesContent":["import { PaperPlane } from '@strapi/icons';\n\nimport { ReleaseAction } from './components/ReleaseAction';\nimport { ReleaseActionModalForm } from './components/ReleaseActionModal';\nimport { addColumnToTableHook } from './components/ReleaseListCell';\nimport { Panel as ReleasesPanel } from './components/ReleasesPanel';\nimport { PERMISSIONS } from './constants';\nimport { pluginId } from './pluginId';\nimport { prefixPluginTranslations } from './utils/prefixPluginTranslations';\n\nimport type { StrapiApp } from '@strapi/admin/strapi-admin';\nimport type {\n DocumentActionComponent,\n BulkActionComponent,\n} from '@strapi/content-manager/strapi-admin';\nimport type { Plugin } from '@strapi/types';\n\n// eslint-disable-next-line import/no-default-export\nconst admin: Plugin.Config.AdminInput = {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n register(app: StrapiApp) {\n /**\n * Hook that adds the locale column in the Release Details table\n * @constant\n * @type {string}\n */\n app.createHook('ContentReleases/pages/ReleaseDetails/add-locale-in-releases');\n\n if (window.strapi.features.isEnabled('cms-content-releases')) {\n app.addMenuLink({\n to: `plugins/${pluginId}`,\n icon: PaperPlane,\n intlLabel: {\n id: `${pluginId}.plugin.name`,\n defaultMessage: 'Releases',\n },\n Component: () => import('./pages/App').then((mod) => ({ default: mod.App })),\n permissions: PERMISSIONS.main,\n position: 2,\n });\n\n // Insert the releases container into the CM's sidebar on the Edit View\n const contentManagerPluginApis = app.getPlugin('content-manager').apis;\n if (\n 'addEditViewSidePanel' in contentManagerPluginApis &&\n typeof contentManagerPluginApis.addEditViewSidePanel === 'function'\n ) {\n contentManagerPluginApis.addEditViewSidePanel([ReleasesPanel]);\n }\n\n // Insert the \"add to release\" action into the CM's Edit View\n if (\n 'addDocumentAction' in contentManagerPluginApis &&\n typeof contentManagerPluginApis.addDocumentAction === 'function'\n ) {\n contentManagerPluginApis.addDocumentAction((actions: DocumentActionComponent[]) => {\n const indexOfDeleteAction = actions.findIndex((action) => action.type === 'unpublish');\n actions.splice(indexOfDeleteAction, 0, ReleaseActionModalForm);\n return actions;\n });\n }\n\n app.addSettingsLink('global', {\n id: pluginId,\n to: 'releases',\n intlLabel: {\n id: `${pluginId}.plugin.name`,\n defaultMessage: 'Releases',\n },\n permissions: [],\n async Component() {\n const { ProtectedReleasesSettingsPage } = await import('./pages/ReleasesSettingsPage');\n return { default: ProtectedReleasesSettingsPage };\n },\n });\n\n if (\n 'addBulkAction' in contentManagerPluginApis &&\n typeof contentManagerPluginApis.addBulkAction === 'function'\n ) {\n contentManagerPluginApis.addBulkAction((actions: BulkActionComponent[]) => {\n // We want to add this action to just before the delete action all the time\n const deleteActionIndex = actions.findIndex((action) => action.type === 'delete');\n\n actions.splice(deleteActionIndex, 0, ReleaseAction);\n return actions;\n });\n }\n\n // Hook that adds a column into the CM's LV table\n app.registerHook('Admin/CM/pages/ListView/inject-column-in-table', addColumnToTableHook);\n } else if (\n !window.strapi.features.isEnabled('cms-content-releases') &&\n window.strapi?.flags?.promoteEE\n ) {\n app.addSettingsLink('global', {\n id: pluginId,\n to: '/plugins/purchase-content-releases',\n intlLabel: {\n id: `${pluginId}.plugin.name`,\n defaultMessage: 'Releases',\n },\n permissions: [],\n async Component() {\n const { PurchaseContentReleases } = await import('./pages/PurchaseContentReleases');\n return { default: PurchaseContentReleases };\n },\n licenseOnly: true,\n });\n }\n },\n async registerTrads({ locales }: { locales: string[] }) {\n const importedTrads = await Promise.all(\n locales.map((locale) => {\n return import(`./translations/${locale}.json`)\n .then(({ default: data }) => {\n return {\n data: prefixPluginTranslations(data, 'content-releases'),\n locale,\n };\n })\n .catch(() => {\n return {\n data: {},\n locale,\n };\n });\n })\n );\n\n return Promise.resolve(importedTrads);\n },\n};\n\n// eslint-disable-next-line import/no-default-export\Line truncated
+122
View File
@@ -0,0 +1,122 @@
import { PaperPlane } from '@strapi/icons';
import { ReleaseAction } from './components/ReleaseAction.mjs';
import { ReleaseActionModalForm } from './components/ReleaseActionModal.mjs';
import { addColumnToTableHook } from './components/ReleaseListCell.mjs';
import { Panel } from './components/ReleasesPanel.mjs';
import { PERMISSIONS } from './constants.mjs';
import { pluginId } from './pluginId.mjs';
import { prefixPluginTranslations } from './utils/prefixPluginTranslations.mjs';
function __variableDynamicImportRuntime3__(path) {
switch (path) {
case './translations/en.json': return import('./translations/en.json.mjs');
case './translations/uk.json': return import('./translations/uk.json.mjs');
default: return new Promise(function(resolve, reject) {
(typeof queueMicrotask === 'function' ? queueMicrotask : setTimeout)(
reject.bind(null, new Error("Unknown variable dynamic import: " + path))
);
})
}
}
// eslint-disable-next-line import/no-default-export
const admin = {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
register (app) {
/**
* Hook that adds the locale column in the Release Details table
* @constant
* @type {string}
*/ app.createHook('ContentReleases/pages/ReleaseDetails/add-locale-in-releases');
if (window.strapi.features.isEnabled('cms-content-releases')) {
app.addMenuLink({
to: `plugins/${pluginId}`,
icon: PaperPlane,
intlLabel: {
id: `${pluginId}.plugin.name`,
defaultMessage: 'Releases'
},
Component: ()=>import('./pages/App.mjs').then((mod)=>({
default: mod.App
})),
permissions: PERMISSIONS.main,
position: 2
});
// Insert the releases container into the CM's sidebar on the Edit View
const contentManagerPluginApis = app.getPlugin('content-manager').apis;
if ('addEditViewSidePanel' in contentManagerPluginApis && typeof contentManagerPluginApis.addEditViewSidePanel === 'function') {
contentManagerPluginApis.addEditViewSidePanel([
Panel
]);
}
// Insert the "add to release" action into the CM's Edit View
if ('addDocumentAction' in contentManagerPluginApis && typeof contentManagerPluginApis.addDocumentAction === 'function') {
contentManagerPluginApis.addDocumentAction((actions)=>{
const indexOfDeleteAction = actions.findIndex((action)=>action.type === 'unpublish');
actions.splice(indexOfDeleteAction, 0, ReleaseActionModalForm);
return actions;
});
}
app.addSettingsLink('global', {
id: pluginId,
to: 'releases',
intlLabel: {
id: `${pluginId}.plugin.name`,
defaultMessage: 'Releases'
},
permissions: [],
async Component () {
const { ProtectedReleasesSettingsPage } = await import('./pages/ReleasesSettingsPage.mjs');
return {
default: ProtectedReleasesSettingsPage
};
}
});
if ('addBulkAction' in contentManagerPluginApis && typeof contentManagerPluginApis.addBulkAction === 'function') {
contentManagerPluginApis.addBulkAction((actions)=>{
// We want to add this action to just before the delete action all the time
const deleteActionIndex = actions.findIndex((action)=>action.type === 'delete');
actions.splice(deleteActionIndex, 0, ReleaseAction);
return actions;
});
}
// Hook that adds a column into the CM's LV table
app.registerHook('Admin/CM/pages/ListView/inject-column-in-table', addColumnToTableHook);
} else if (!window.strapi.features.isEnabled('cms-content-releases') && window.strapi?.flags?.promoteEE) {
app.addSettingsLink('global', {
id: pluginId,
to: '/plugins/purchase-content-releases',
intlLabel: {
id: `${pluginId}.plugin.name`,
defaultMessage: 'Releases'
},
permissions: [],
async Component () {
const { PurchaseContentReleases } = await import('./pages/PurchaseContentReleases.mjs');
return {
default: PurchaseContentReleases
};
},
licenseOnly: true
});
}
},
async registerTrads ({ locales }) {
const importedTrads = await Promise.all(locales.map((locale)=>{
return __variableDynamicImportRuntime3__(`./translations/${locale}.json`).then(({ default: data })=>{
return {
data: prefixPluginTranslations(data, 'content-releases'),
locale
};
}).catch(()=>{
return {
data: {},
locale
};
});
}));
return Promise.resolve(importedTrads);
}
};
export { admin as default };
//# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
{"version":3,"file":"index.mjs","sources":["../../admin/src/index.ts"],"sourcesContent":["import { PaperPlane } from '@strapi/icons';\n\nimport { ReleaseAction } from './components/ReleaseAction';\nimport { ReleaseActionModalForm } from './components/ReleaseActionModal';\nimport { addColumnToTableHook } from './components/ReleaseListCell';\nimport { Panel as ReleasesPanel } from './components/ReleasesPanel';\nimport { PERMISSIONS } from './constants';\nimport { pluginId } from './pluginId';\nimport { prefixPluginTranslations } from './utils/prefixPluginTranslations';\n\nimport type { StrapiApp } from '@strapi/admin/strapi-admin';\nimport type {\n DocumentActionComponent,\n BulkActionComponent,\n} from '@strapi/content-manager/strapi-admin';\nimport type { Plugin } from '@strapi/types';\n\n// eslint-disable-next-line import/no-default-export\nconst admin: Plugin.Config.AdminInput = {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n register(app: StrapiApp) {\n /**\n * Hook that adds the locale column in the Release Details table\n * @constant\n * @type {string}\n */\n app.createHook('ContentReleases/pages/ReleaseDetails/add-locale-in-releases');\n\n if (window.strapi.features.isEnabled('cms-content-releases')) {\n app.addMenuLink({\n to: `plugins/${pluginId}`,\n icon: PaperPlane,\n intlLabel: {\n id: `${pluginId}.plugin.name`,\n defaultMessage: 'Releases',\n },\n Component: () => import('./pages/App').then((mod) => ({ default: mod.App })),\n permissions: PERMISSIONS.main,\n position: 2,\n });\n\n // Insert the releases container into the CM's sidebar on the Edit View\n const contentManagerPluginApis = app.getPlugin('content-manager').apis;\n if (\n 'addEditViewSidePanel' in contentManagerPluginApis &&\n typeof contentManagerPluginApis.addEditViewSidePanel === 'function'\n ) {\n contentManagerPluginApis.addEditViewSidePanel([ReleasesPanel]);\n }\n\n // Insert the \"add to release\" action into the CM's Edit View\n if (\n 'addDocumentAction' in contentManagerPluginApis &&\n typeof contentManagerPluginApis.addDocumentAction === 'function'\n ) {\n contentManagerPluginApis.addDocumentAction((actions: DocumentActionComponent[]) => {\n const indexOfDeleteAction = actions.findIndex((action) => action.type === 'unpublish');\n actions.splice(indexOfDeleteAction, 0, ReleaseActionModalForm);\n return actions;\n });\n }\n\n app.addSettingsLink('global', {\n id: pluginId,\n to: 'releases',\n intlLabel: {\n id: `${pluginId}.plugin.name`,\n defaultMessage: 'Releases',\n },\n permissions: [],\n async Component() {\n const { ProtectedReleasesSettingsPage } = await import('./pages/ReleasesSettingsPage');\n return { default: ProtectedReleasesSettingsPage };\n },\n });\n\n if (\n 'addBulkAction' in contentManagerPluginApis &&\n typeof contentManagerPluginApis.addBulkAction === 'function'\n ) {\n contentManagerPluginApis.addBulkAction((actions: BulkActionComponent[]) => {\n // We want to add this action to just before the delete action all the time\n const deleteActionIndex = actions.findIndex((action) => action.type === 'delete');\n\n actions.splice(deleteActionIndex, 0, ReleaseAction);\n return actions;\n });\n }\n\n // Hook that adds a column into the CM's LV table\n app.registerHook('Admin/CM/pages/ListView/inject-column-in-table', addColumnToTableHook);\n } else if (\n !window.strapi.features.isEnabled('cms-content-releases') &&\n window.strapi?.flags?.promoteEE\n ) {\n app.addSettingsLink('global', {\n id: pluginId,\n to: '/plugins/purchase-content-releases',\n intlLabel: {\n id: `${pluginId}.plugin.name`,\n defaultMessage: 'Releases',\n },\n permissions: [],\n async Component() {\n const { PurchaseContentReleases } = await import('./pages/PurchaseContentReleases');\n return { default: PurchaseContentReleases };\n },\n licenseOnly: true,\n });\n }\n },\n async registerTrads({ locales }: { locales: string[] }) {\n const importedTrads = await Promise.all(\n locales.map((locale) => {\n return import(`./translations/${locale}.json`)\n .then(({ default: data }) => {\n return {\n data: prefixPluginTranslations(data, 'content-releases'),\n locale,\n };\n })\n .catch(() => {\n return {\n data: {},\n locale,\n };\n });\n })\n );\n\n return Promise.resolve(importedTrads);\n },\n};\n\n// eslint-disable-next-line import/no-default-exportLine truncated
@@ -0,0 +1,8 @@
'use strict';
var reactRedux = require('react-redux');
const useTypedSelector = reactRedux.useSelector;
exports.useTypedSelector = useTypedSelector;
//# sourceMappingURL=hooks.js.map
@@ -0,0 +1 @@
{"version":3,"file":"hooks.js","sources":["../../../admin/src/modules/hooks.ts"],"sourcesContent":["import { Dispatch } from '@reduxjs/toolkit';\nimport { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';\n\nimport type { Store } from '@strapi/admin/strapi-admin';\n\ntype RootState = ReturnType<Store['getState']>;\n\nconst useTypedDispatch: () => Dispatch = useDispatch;\nconst useTypedSelector: TypedUseSelectorHook<RootState> = useSelector;\n\nexport { useTypedSelector, useTypedDispatch };\n"],"names":["useTypedSelector","useSelector"],"mappings":";;;;AAQA,MAAMA,gBAAoDC,GAAAA;;;;"}
@@ -0,0 +1,6 @@
import { useSelector } from 'react-redux';
const useTypedSelector = useSelector;
export { useTypedSelector };
//# sourceMappingURL=hooks.mjs.map
@@ -0,0 +1 @@
{"version":3,"file":"hooks.mjs","sources":["../../../admin/src/modules/hooks.ts"],"sourcesContent":["import { Dispatch } from '@reduxjs/toolkit';\nimport { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';\n\nimport type { Store } from '@strapi/admin/strapi-admin';\n\ntype RootState = ReturnType<Store['getState']>;\n\nconst useTypedDispatch: () => Dispatch = useDispatch;\nconst useTypedSelector: TypedUseSelectorHook<RootState> = useSelector;\n\nexport { useTypedSelector, useTypedDispatch };\n"],"names":["useTypedSelector","useSelector"],"mappings":";;AAQA,MAAMA,gBAAoDC,GAAAA;;;;"}
+29
View File
@@ -0,0 +1,29 @@
'use strict';
var jsxRuntime = require('react/jsx-runtime');
var strapiAdmin = require('@strapi/admin/strapi-admin');
var reactRouterDom = require('react-router-dom');
var constants = require('../constants.js');
var ReleaseDetailsPage = require('./ReleaseDetailsPage.js');
var ReleasesPage = require('./ReleasesPage.js');
const App = ()=>{
return /*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.Page.Protect, {
permissions: constants.PERMISSIONS.main,
children: /*#__PURE__*/ jsxRuntime.jsxs(reactRouterDom.Routes, {
children: [
/*#__PURE__*/ jsxRuntime.jsx(reactRouterDom.Route, {
index: true,
element: /*#__PURE__*/ jsxRuntime.jsx(ReleasesPage.ReleasesPage, {})
}),
/*#__PURE__*/ jsxRuntime.jsx(reactRouterDom.Route, {
path: ':releaseId',
element: /*#__PURE__*/ jsxRuntime.jsx(ReleaseDetailsPage.ReleaseDetailsPage, {})
})
]
})
});
};
exports.App = App;
//# sourceMappingURL=App.js.map
@@ -0,0 +1 @@
{"version":3,"file":"App.js","sources":["../../../admin/src/pages/App.tsx"],"sourcesContent":["import { Page } from '@strapi/admin/strapi-admin';\nimport { Route, Routes } from 'react-router-dom';\n\nimport { PERMISSIONS } from '../constants';\n\nimport { ReleaseDetailsPage } from './ReleaseDetailsPage';\nimport { ReleasesPage } from './ReleasesPage';\n\nexport const App = () => {\n return (\n <Page.Protect permissions={PERMISSIONS.main}>\n <Routes>\n <Route index element={<ReleasesPage />} />\n <Route path={':releaseId'} element={<ReleaseDetailsPage />} />\n </Routes>\n </Page.Protect>\n );\n};\n"],"names":["App","_jsx","Page","Protect","permissions","PERMISSIONS","main","_jsxs","Routes","Route","index","element","ReleasesPage","path","ReleaseDetailsPage"],"mappings":";;;;;;;;;MAQaA,GAAM,GAAA,IAAA;IACjB,qBACEC,cAAA,CAACC,iBAAKC,OAAO,EAAA;AAACC,QAAAA,WAAAA,EAAaC,sBAAYC,IAAI;AACzC,QAAA,QAAA,gBAAAC,eAACC,CAAAA,qBAAAA,EAAAA;;8BACCP,cAACQ,CAAAA,oBAAAA,EAAAA;oBAAMC,KAAK,EAAA,IAAA;AAACC,oBAAAA,OAAAA,gBAASV,cAACW,CAAAA,yBAAAA,EAAAA,EAAAA;;8BACvBX,cAACQ,CAAAA,oBAAAA,EAAAA;oBAAMI,IAAM,EAAA,YAAA;AAAcF,oBAAAA,OAAAA,gBAASV,cAACa,CAAAA,qCAAAA,EAAAA,EAAAA;;;;;AAI7C;;;;"}
+27
View File
@@ -0,0 +1,27 @@
import { jsx, jsxs } from 'react/jsx-runtime';
import { Page } from '@strapi/admin/strapi-admin';
import { Routes, Route } from 'react-router-dom';
import { PERMISSIONS } from '../constants.mjs';
import { ReleaseDetailsPage } from './ReleaseDetailsPage.mjs';
import { ReleasesPage } from './ReleasesPage.mjs';
const App = ()=>{
return /*#__PURE__*/ jsx(Page.Protect, {
permissions: PERMISSIONS.main,
children: /*#__PURE__*/ jsxs(Routes, {
children: [
/*#__PURE__*/ jsx(Route, {
index: true,
element: /*#__PURE__*/ jsx(ReleasesPage, {})
}),
/*#__PURE__*/ jsx(Route, {
path: ':releaseId',
element: /*#__PURE__*/ jsx(ReleaseDetailsPage, {})
})
]
})
});
};
export { App };
//# sourceMappingURL=App.mjs.map
@@ -0,0 +1 @@
{"version":3,"file":"App.mjs","sources":["../../../admin/src/pages/App.tsx"],"sourcesContent":["import { Page } from '@strapi/admin/strapi-admin';\nimport { Route, Routes } from 'react-router-dom';\n\nimport { PERMISSIONS } from '../constants';\n\nimport { ReleaseDetailsPage } from './ReleaseDetailsPage';\nimport { ReleasesPage } from './ReleasesPage';\n\nexport const App = () => {\n return (\n <Page.Protect permissions={PERMISSIONS.main}>\n <Routes>\n <Route index element={<ReleasesPage />} />\n <Route path={':releaseId'} element={<ReleaseDetailsPage />} />\n </Routes>\n </Page.Protect>\n );\n};\n"],"names":["App","_jsx","Page","Protect","permissions","PERMISSIONS","main","_jsxs","Routes","Route","index","element","ReleasesPage","path","ReleaseDetailsPage"],"mappings":";;;;;;;MAQaA,GAAM,GAAA,IAAA;IACjB,qBACEC,GAAA,CAACC,KAAKC,OAAO,EAAA;AAACC,QAAAA,WAAAA,EAAaC,YAAYC,IAAI;AACzC,QAAA,QAAA,gBAAAC,IAACC,CAAAA,MAAAA,EAAAA;;8BACCP,GAACQ,CAAAA,KAAAA,EAAAA;oBAAMC,KAAK,EAAA,IAAA;AAACC,oBAAAA,OAAAA,gBAASV,GAACW,CAAAA,YAAAA,EAAAA,EAAAA;;8BACvBX,GAACQ,CAAAA,KAAAA,EAAAA;oBAAMI,IAAM,EAAA,YAAA;AAAcF,oBAAAA,OAAAA,gBAASV,GAACa,CAAAA,kBAAAA,EAAAA,EAAAA;;;;;AAI7C;;;;"}
@@ -0,0 +1,192 @@
'use strict';
var jsxRuntime = require('react/jsx-runtime');
var strapiAdmin = require('@strapi/admin/strapi-admin');
var designSystem = require('@strapi/design-system');
var icons = require('@strapi/icons');
var reactIntl = require('react-intl');
var purchasePageIllustrationDark = require('../assets/purchase-page-illustration-dark.svg.js');
var purchasePageIllustrationLight = require('../assets/purchase-page-illustration-light.svg.js');
var hooks = require('../modules/hooks.js');
const PurchaseContentReleases = ()=>{
const { formatMessage } = reactIntl.useIntl();
const currentTheme = hooks.useTypedSelector((state)=>state.admin_app.theme.currentTheme);
const illustration = currentTheme === 'light' ? purchasePageIllustrationLight : purchasePageIllustrationDark;
return /*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.Layouts.Root, {
children: /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Main, {
children: [
/*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.Layouts.Header, {
title: formatMessage({
id: 'content-releases.pages.Releases.title',
defaultMessage: 'Releases'
})
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Box, {
marginLeft: 10,
marginRight: 10,
shadow: "filterShadow",
hasRadius: true,
background: "neutral0",
borderColor: 'neutral150',
overflow: 'hidden',
children: /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Grid.Root, {
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Grid.Item, {
col: 6,
s: 12,
alignItems: 'flex-start',
children: /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
direction: "column",
alignItems: "flex-start",
padding: 7,
width: '100%',
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Flex, {
children: /*#__PURE__*/ jsxRuntime.jsx(icons.PaperPlane, {
fill: "primary600",
width: `24px`,
height: `24px`
})
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Flex, {
paddingTop: 3,
paddingBottom: 4,
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
variant: "beta",
fontWeight: "bold",
children: formatMessage({
id: 'pages.PurchaseRelease.description',
defaultMessage: 'Group content and publish updates together'
})
})
}),
/*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
direction: "column",
alignItems: 'flex-start',
gap: 2,
children: [
/*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
gap: 2,
children: [
/*#__PURE__*/ jsxRuntime.jsx(icons.Check, {
fill: "success500",
width: `16px`,
height: `16px`,
style: {
flexShrink: 0
}
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
textColor: "neutral700",
children: formatMessage({
id: 'pages.PurchaseRelease.perks1',
defaultMessage: 'Add many entries to releases'
})
})
]
}),
/*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
gap: 2,
children: [
/*#__PURE__*/ jsxRuntime.jsx(icons.Check, {
fill: "success500",
width: `16px`,
height: `16px`,
style: {
flexShrink: 0
}
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
textColor: "neutral700",
children: formatMessage({
id: 'pages.PurchaseRelease.perks2',
defaultMessage: 'Quickly identify entries containing errors'
})
})
]
}),
/*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
gap: 2,
children: [
/*#__PURE__*/ jsxRuntime.jsx(icons.Check, {
fill: "success500",
width: `16px`,
height: `16px`,
style: {
flexShrink: 0
}
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
textColor: "neutral700",
children: formatMessage({
id: 'pages.PurchaseRelease.perks3',
defaultMessage: 'Schedule their publication, or publish them manually'
})
})
]
})
]
}),
/*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
gap: 2,
marginTop: 7,
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.LinkButton, {
variant: "default",
href: "https://strapi.io/pricing-self-hosted?utm_campaign=In-Product-CTA&utm_source=Releases",
children: formatMessage({
id: 'Settings.page.purchase.upgrade.cta',
defaultMessage: 'Upgrade'
})
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.LinkButton, {
variant: "tertiary",
endIcon: /*#__PURE__*/ jsxRuntime.jsx(icons.ExternalLink, {}),
href: "https://strapi.io/features/releases?utm_campaign=In-Product-CTA&utm_source=Releases",
children: formatMessage({
id: 'Settings.page.purchase.learn-more.cta',
defaultMessage: 'Learn more'
})
})
]
})
]
})
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Grid.Item, {
col: 6,
s: 12,
background: "primary100",
minHeight: '280px',
children: /*#__PURE__*/ jsxRuntime.jsx("div", {
style: {
position: 'relative',
width: '100%',
height: '100%'
},
children: /*#__PURE__*/ jsxRuntime.jsx("img", {
src: illustration,
alt: "purchase-page-content-releases-illustration",
style: {
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%',
objectFit: 'cover',
objectPosition: 'top left'
}
})
})
})
]
})
})
]
})
});
};
exports.PurchaseContentReleases = PurchaseContentReleases;
//# sourceMappingURL=PurchaseContentReleases.js.map
@@ -0,0 +1 @@
{"version":3,"file":"PurchaseContentReleases.js","sources":["../../../admin/src/pages/PurchaseContentReleases.tsx"],"sourcesContent":["import { Layouts } from '@strapi/admin/strapi-admin';\nimport { Box, Main, Flex, Typography, Grid, LinkButton } from '@strapi/design-system';\nimport { ExternalLink, Check, PaperPlane } from '@strapi/icons';\nimport { useIntl } from 'react-intl';\n\nimport darkIllustration from '../assets/purchase-page-illustration-dark.svg';\nimport lightIllustration from '../assets/purchase-page-illustration-light.svg';\nimport { useTypedSelector } from '../modules/hooks';\n\nconst PurchaseContentReleases = () => {\n const { formatMessage } = useIntl();\n const currentTheme = useTypedSelector((state) => state.admin_app.theme.currentTheme);\n\n const illustration = currentTheme === 'light' ? lightIllustration : darkIllustration;\n return (\n <Layouts.Root>\n <Main>\n <Layouts.Header\n title={formatMessage({\n id: 'content-releases.pages.Releases.title',\n defaultMessage: 'Releases',\n })}\n />\n <Box\n marginLeft={10}\n marginRight={10}\n shadow=\"filterShadow\"\n hasRadius\n background=\"neutral0\"\n borderColor={'neutral150'}\n overflow={'hidden'}\n >\n <Grid.Root>\n <Grid.Item col={6} s={12} alignItems={'flex-start'}>\n <Flex direction=\"column\" alignItems=\"flex-start\" padding={7} width={'100%'}>\n <Flex>\n <PaperPlane fill=\"primary600\" width={`24px`} height={`24px`} />\n </Flex>\n <Flex paddingTop={3} paddingBottom={4}>\n <Typography variant=\"beta\" fontWeight=\"bold\">\n {formatMessage({\n id: 'pages.PurchaseRelease.description',\n defaultMessage: 'Group content and publish updates together',\n })}\n </Typography>\n </Flex>\n\n <Flex direction=\"column\" alignItems={'flex-start'} gap={2}>\n <Flex gap={2}>\n <Check\n fill=\"success500\"\n width={`16px`}\n height={`16px`}\n style={{ flexShrink: 0 }}\n />\n <Typography textColor=\"neutral700\">\n {formatMessage({\n id: 'pages.PurchaseRelease.perks1',\n defaultMessage: 'Add many entries to releases',\n })}\n </Typography>\n </Flex>\n\n <Flex gap={2}>\n <Check\n fill=\"success500\"\n width={`16px`}\n height={`16px`}\n style={{ flexShrink: 0 }}\n />\n <Typography textColor=\"neutral700\">\n {formatMessage({\n id: 'pages.PurchaseRelease.perks2',\n defaultMessage: 'Quickly identify entries containing errors',\n })}\n </Typography>\n </Flex>\n\n <Flex gap={2}>\n <Check\n fill=\"success500\"\n width={`16px`}\n height={`16px`}\n style={{ flexShrink: 0 }}\n />\n <Typography textColor=\"neutral700\">\n {formatMessage({\n id: 'pages.PurchaseRelease.perks3',\n defaultMessage: 'Schedule their publication, or publish them manually',\n })}\n </Typography>\n </Flex>\n </Flex>\n\n <Flex gap={2} marginTop={7}>\n <LinkButton\n variant=\"default\"\n href=\"https://strapi.io/pricing-self-hosted?utm_campaign=In-Product-CTA&utm_source=Releases\"\n >\n {formatMessage({\n id: 'Settings.page.purchase.upgrade.cta',\n defaultMessage: 'Upgrade',\n })}\n </LinkButton>\n <LinkButton\n variant=\"tertiary\"\n endIcon={<ExternalLink />}\n href=\"https://strapi.io/features/releases?utm_campaign=In-Product-CTA&utm_source=Releases\"\n >\n {formatMessage({\n id: 'Settings.page.purchase.learn-more.cta',\n defaultMessage: 'Learn more',\n })}\n </LinkButton>\n </Flex>\n </Flex>\n </Grid.Item>\n <Grid.Item col={6} s={12} background=\"primarLine truncated
@@ -0,0 +1,190 @@
import { jsx, jsxs } from 'react/jsx-runtime';
import { Layouts } from '@strapi/admin/strapi-admin';
import { Main, Box, Grid, Flex, Typography, LinkButton } from '@strapi/design-system';
import { PaperPlane, Check, ExternalLink } from '@strapi/icons';
import { useIntl } from 'react-intl';
import img$1 from '../assets/purchase-page-illustration-dark.svg.mjs';
import img from '../assets/purchase-page-illustration-light.svg.mjs';
import { useTypedSelector } from '../modules/hooks.mjs';
const PurchaseContentReleases = ()=>{
const { formatMessage } = useIntl();
const currentTheme = useTypedSelector((state)=>state.admin_app.theme.currentTheme);
const illustration = currentTheme === 'light' ? img : img$1;
return /*#__PURE__*/ jsx(Layouts.Root, {
children: /*#__PURE__*/ jsxs(Main, {
children: [
/*#__PURE__*/ jsx(Layouts.Header, {
title: formatMessage({
id: 'content-releases.pages.Releases.title',
defaultMessage: 'Releases'
})
}),
/*#__PURE__*/ jsx(Box, {
marginLeft: 10,
marginRight: 10,
shadow: "filterShadow",
hasRadius: true,
background: "neutral0",
borderColor: 'neutral150',
overflow: 'hidden',
children: /*#__PURE__*/ jsxs(Grid.Root, {
children: [
/*#__PURE__*/ jsx(Grid.Item, {
col: 6,
s: 12,
alignItems: 'flex-start',
children: /*#__PURE__*/ jsxs(Flex, {
direction: "column",
alignItems: "flex-start",
padding: 7,
width: '100%',
children: [
/*#__PURE__*/ jsx(Flex, {
children: /*#__PURE__*/ jsx(PaperPlane, {
fill: "primary600",
width: `24px`,
height: `24px`
})
}),
/*#__PURE__*/ jsx(Flex, {
paddingTop: 3,
paddingBottom: 4,
children: /*#__PURE__*/ jsx(Typography, {
variant: "beta",
fontWeight: "bold",
children: formatMessage({
id: 'pages.PurchaseRelease.description',
defaultMessage: 'Group content and publish updates together'
})
})
}),
/*#__PURE__*/ jsxs(Flex, {
direction: "column",
alignItems: 'flex-start',
gap: 2,
children: [
/*#__PURE__*/ jsxs(Flex, {
gap: 2,
children: [
/*#__PURE__*/ jsx(Check, {
fill: "success500",
width: `16px`,
height: `16px`,
style: {
flexShrink: 0
}
}),
/*#__PURE__*/ jsx(Typography, {
textColor: "neutral700",
children: formatMessage({
id: 'pages.PurchaseRelease.perks1',
defaultMessage: 'Add many entries to releases'
})
})
]
}),
/*#__PURE__*/ jsxs(Flex, {
gap: 2,
children: [
/*#__PURE__*/ jsx(Check, {
fill: "success500",
width: `16px`,
height: `16px`,
style: {
flexShrink: 0
}
}),
/*#__PURE__*/ jsx(Typography, {
textColor: "neutral700",
children: formatMessage({
id: 'pages.PurchaseRelease.perks2',
defaultMessage: 'Quickly identify entries containing errors'
})
})
]
}),
/*#__PURE__*/ jsxs(Flex, {
gap: 2,
children: [
/*#__PURE__*/ jsx(Check, {
fill: "success500",
width: `16px`,
height: `16px`,
style: {
flexShrink: 0
}
}),
/*#__PURE__*/ jsx(Typography, {
textColor: "neutral700",
children: formatMessage({
id: 'pages.PurchaseRelease.perks3',
defaultMessage: 'Schedule their publication, or publish them manually'
})
})
]
})
]
}),
/*#__PURE__*/ jsxs(Flex, {
gap: 2,
marginTop: 7,
children: [
/*#__PURE__*/ jsx(LinkButton, {
variant: "default",
href: "https://strapi.io/pricing-self-hosted?utm_campaign=In-Product-CTA&utm_source=Releases",
children: formatMessage({
id: 'Settings.page.purchase.upgrade.cta',
defaultMessage: 'Upgrade'
})
}),
/*#__PURE__*/ jsx(LinkButton, {
variant: "tertiary",
endIcon: /*#__PURE__*/ jsx(ExternalLink, {}),
href: "https://strapi.io/features/releases?utm_campaign=In-Product-CTA&utm_source=Releases",
children: formatMessage({
id: 'Settings.page.purchase.learn-more.cta',
defaultMessage: 'Learn more'
})
})
]
})
]
})
}),
/*#__PURE__*/ jsx(Grid.Item, {
col: 6,
s: 12,
background: "primary100",
minHeight: '280px',
children: /*#__PURE__*/ jsx("div", {
style: {
position: 'relative',
width: '100%',
height: '100%'
},
children: /*#__PURE__*/ jsx("img", {
src: illustration,
alt: "purchase-page-content-releases-illustration",
style: {
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%',
objectFit: 'cover',
objectPosition: 'top left'
}
})
})
})
]
})
})
]
})
});
};
export { PurchaseContentReleases };
//# sourceMappingURL=PurchaseContentReleases.mjs.map
@@ -0,0 +1 @@
{"version":3,"file":"PurchaseContentReleases.mjs","sources":["../../../admin/src/pages/PurchaseContentReleases.tsx"],"sourcesContent":["import { Layouts } from '@strapi/admin/strapi-admin';\nimport { Box, Main, Flex, Typography, Grid, LinkButton } from '@strapi/design-system';\nimport { ExternalLink, Check, PaperPlane } from '@strapi/icons';\nimport { useIntl } from 'react-intl';\n\nimport darkIllustration from '../assets/purchase-page-illustration-dark.svg';\nimport lightIllustration from '../assets/purchase-page-illustration-light.svg';\nimport { useTypedSelector } from '../modules/hooks';\n\nconst PurchaseContentReleases = () => {\n const { formatMessage } = useIntl();\n const currentTheme = useTypedSelector((state) => state.admin_app.theme.currentTheme);\n\n const illustration = currentTheme === 'light' ? lightIllustration : darkIllustration;\n return (\n <Layouts.Root>\n <Main>\n <Layouts.Header\n title={formatMessage({\n id: 'content-releases.pages.Releases.title',\n defaultMessage: 'Releases',\n })}\n />\n <Box\n marginLeft={10}\n marginRight={10}\n shadow=\"filterShadow\"\n hasRadius\n background=\"neutral0\"\n borderColor={'neutral150'}\n overflow={'hidden'}\n >\n <Grid.Root>\n <Grid.Item col={6} s={12} alignItems={'flex-start'}>\n <Flex direction=\"column\" alignItems=\"flex-start\" padding={7} width={'100%'}>\n <Flex>\n <PaperPlane fill=\"primary600\" width={`24px`} height={`24px`} />\n </Flex>\n <Flex paddingTop={3} paddingBottom={4}>\n <Typography variant=\"beta\" fontWeight=\"bold\">\n {formatMessage({\n id: 'pages.PurchaseRelease.description',\n defaultMessage: 'Group content and publish updates together',\n })}\n </Typography>\n </Flex>\n\n <Flex direction=\"column\" alignItems={'flex-start'} gap={2}>\n <Flex gap={2}>\n <Check\n fill=\"success500\"\n width={`16px`}\n height={`16px`}\n style={{ flexShrink: 0 }}\n />\n <Typography textColor=\"neutral700\">\n {formatMessage({\n id: 'pages.PurchaseRelease.perks1',\n defaultMessage: 'Add many entries to releases',\n })}\n </Typography>\n </Flex>\n\n <Flex gap={2}>\n <Check\n fill=\"success500\"\n width={`16px`}\n height={`16px`}\n style={{ flexShrink: 0 }}\n />\n <Typography textColor=\"neutral700\">\n {formatMessage({\n id: 'pages.PurchaseRelease.perks2',\n defaultMessage: 'Quickly identify entries containing errors',\n })}\n </Typography>\n </Flex>\n\n <Flex gap={2}>\n <Check\n fill=\"success500\"\n width={`16px`}\n height={`16px`}\n style={{ flexShrink: 0 }}\n />\n <Typography textColor=\"neutral700\">\n {formatMessage({\n id: 'pages.PurchaseRelease.perks3',\n defaultMessage: 'Schedule their publication, or publish them manually',\n })}\n </Typography>\n </Flex>\n </Flex>\n\n <Flex gap={2} marginTop={7}>\n <LinkButton\n variant=\"default\"\n href=\"https://strapi.io/pricing-self-hosted?utm_campaign=In-Product-CTA&utm_source=Releases\"\n >\n {formatMessage({\n id: 'Settings.page.purchase.upgrade.cta',\n defaultMessage: 'Upgrade',\n })}\n </LinkButton>\n <LinkButton\n variant=\"tertiary\"\n endIcon={<ExternalLink />}\n href=\"https://strapi.io/features/releases?utm_campaign=In-Product-CTA&utm_source=Releases\"\n >\n {formatMessage({\n id: 'Settings.page.purchase.learn-more.cta',\n defaultMessage: 'Learn more',\n })}\n </LinkButton>\n </Flex>\n </Flex>\n </Grid.Item>\n <Grid.Item col={6} s={12} background=\"primaLine truncated
@@ -0,0 +1,821 @@
'use strict';
var jsxRuntime = require('react/jsx-runtime');
var React = require('react');
var strapiAdmin = require('@strapi/admin/strapi-admin');
var designSystem = require('@strapi/design-system');
var icons = require('@strapi/icons');
var symbols = require('@strapi/icons/symbols');
var format = require('date-fns/format');
var dateFnsTz = require('date-fns-tz');
var reactIntl = require('react-intl');
var reactRouterDom = require('react-router-dom');
var styledComponents = require('styled-components');
var EntryValidationPopover = require('../components/EntryValidationPopover.js');
var RelativeTime = require('../components/RelativeTime.js');
var ReleaseActionMenu = require('../components/ReleaseActionMenu.js');
var ReleaseActionOptions = require('../components/ReleaseActionOptions.js');
var ReleaseModal = require('../components/ReleaseModal.js');
var constants = require('../constants.js');
var release = require('../services/release.js');
var hooks = require('../store/hooks.js');
var api = require('../utils/api.js');
var time = require('../utils/time.js');
var ReleasesPage = require('./ReleasesPage.js');
function _interopNamespaceDefault(e) {
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n.default = e;
return Object.freeze(n);
}
var React__namespace = /*#__PURE__*/_interopNamespaceDefault(React);
/* -------------------------------------------------------------------------------------------------
* ReleaseDetailsLayout
* -----------------------------------------------------------------------------------------------*/ const ReleaseInfoWrapper = styledComponents.styled(designSystem.Flex)`
align-self: stretch;
border-bottom-right-radius: ${({ theme })=>theme.borderRadius};
border-bottom-left-radius: ${({ theme })=>theme.borderRadius};
border-top: 1px solid ${({ theme })=>theme.colors.neutral150};
`;
const StyledMenuItem = styledComponents.styled(designSystem.MenuItem)`
svg path {
fill: ${({ theme, disabled })=>disabled && theme.colors.neutral500};
}
span {
color: ${({ theme, disabled })=>disabled && theme.colors.neutral500};
}
&:hover {
background: ${({ theme, $variant = 'neutral' })=>theme.colors[`${$variant}100`]};
}
`;
const PencilIcon = styledComponents.styled(icons.Pencil)`
width: ${({ theme })=>theme.spaces[4]};
height: ${({ theme })=>theme.spaces[4]};
path {
fill: ${({ theme })=>theme.colors.neutral600};
}
`;
const TrashIcon = styledComponents.styled(icons.Trash)`
width: ${({ theme })=>theme.spaces[4]};
height: ${({ theme })=>theme.spaces[4]};
path {
fill: ${({ theme })=>theme.colors.danger600};
}
`;
const ReleaseDetailsLayout = ({ toggleEditReleaseModal, toggleWarningSubmit, children })=>{
const { formatMessage, formatDate, formatTime } = reactIntl.useIntl();
const { releaseId } = reactRouterDom.useParams();
const { data, isLoading: isLoadingDetails, error } = release.useGetReleaseQuery({
id: releaseId
}, {
skip: !releaseId
});
const [publishRelease, { isLoading: isPublishing }] = release.usePublishReleaseMutation();
const { toggleNotification } = strapiAdmin.useNotification();
const { formatAPIError } = strapiAdmin.useAPIErrorHandler();
const { allowedActions } = strapiAdmin.useRBAC(constants.PERMISSIONS);
const { canUpdate, canDelete, canPublish } = allowedActions;
const dispatch = hooks.useTypedDispatch();
const { trackUsage } = strapiAdmin.useTracking();
const release$1 = data?.data;
const handlePublishRelease = (id)=>async ()=>{
const response = await publishRelease({
id
});
if ('data' in response) {
// When the response returns an object with 'data', handle success
toggleNotification({
type: 'success',
message: formatMessage({
id: 'content-releases.pages.ReleaseDetails.publish-notification-success',
defaultMessage: 'Release was published successfully.'
})
});
const { totalEntries, totalPublishedEntries, totalUnpublishedEntries } = response.data.meta;
trackUsage('didPublishRelease', {
totalEntries,
totalPublishedEntries,
totalUnpublishedEntries
});
} else if (strapiAdmin.isFetchError(response.error)) {
// When the response returns an object with 'error', handle fetch error
toggleNotification({
type: 'danger',
message: formatAPIError(response.error)
});
} else {
// Otherwise, the response returns an object with 'error', handle a generic error
toggleNotification({
type: 'danger',
message: formatMessage({
id: 'notification.error',
defaultMessage: 'An error occurred'
})
});
}
};
const handleRefresh = ()=>{
dispatch(release.releaseApi.util.invalidateTags([
{
type: 'ReleaseAction',
id: 'LIST'
},
{
type: 'Release',
id: releaseId
}
]));
};
const getCreatedByUser = ()=>{
if (!release$1?.createdBy) {
return null;
}
// Favor the username
if (release$1.createdBy.username) {
return release$1.createdBy.username;
}
// Firstname may not exist if created with SSO
if (release$1.createdBy.firstname) {
return `${release$1.createdBy.firstname} ${release$1.createdBy.lastname || ''}`.trim();
}
// All users must have at least an email
return release$1.createdBy.email;
};
if (isLoadingDetails) {
return /*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.Page.Loading, {});
}
if (api.isBaseQueryError(error) && 'code' in error || !release$1) {
return /*#__PURE__*/ jsxRuntime.jsx(reactRouterDom.Navigate, {
to: "..",
state: {
errors: [
{
// @ts-expect-error – TODO: fix this weird error flow
code: error?.code
}
]
}
});
}
const totalEntries = release$1.actions.meta.count || 0;
const hasCreatedByUser = Boolean(getCreatedByUser());
const isScheduled = release$1.scheduledAt && release$1.timezone;
const numberOfEntriesText = formatMessage({
id: 'content-releases.pages.Details.header-subtitle',
defaultMessage: '{number, plural, =0 {No entries} one {# entry} other {# entries}}'
}, {
number: totalEntries
});
const scheduledText = isScheduled ? formatMessage({
id: 'content-releases.pages.ReleaseDetails.header-subtitle.scheduled',
defaultMessage: 'Scheduled for {date} at {time} ({offset})'
}, {
date: formatDate(new Date(release$1.scheduledAt), {
weekday: 'long',
day: 'numeric',
month: 'long',
year: 'numeric',
timeZone: release$1.timezone
}),
time: formatTime(new Date(release$1.scheduledAt), {
timeZone: release$1.timezone,
hourCycle: 'h23'
}),
offset: time.getTimezoneOffset(release$1.timezone, new Date(release$1.scheduledAt))
}) : '';
return /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Main, {
"aria-busy": isLoadingDetails,
children: [
/*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.Layouts.Header, {
title: release$1.name,
subtitle: /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
gap: 2,
lineHeight: 6,
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
textColor: "neutral600",
variant: "epsilon",
children: numberOfEntriesText + (isScheduled ? ` - ${scheduledText}` : '')
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Badge, {
...ReleasesPage.getBadgeProps(release$1.status),
children: release$1.status
})
]
}),
navigationAction: /*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.BackButton, {
fallback: ".."
}),
primaryAction: !release$1.releasedAt && /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
gap: 2,
children: [
/*#__PURE__*/ jsxRuntime.jsxs(SimpleMenuButton, {
label: /*#__PURE__*/ jsxRuntime.jsx(icons.More, {}),
variant: "tertiary",
endIcon: null,
paddingLeft: "7px",
paddingRight: "7px",
"aria-label": formatMessage({
id: 'content-releases.header.actions.open-release-actions',
defaultMessage: 'Release edit and delete menu'
}),
popoverPlacement: "bottom-end",
children: [
/*#__PURE__*/ jsxRuntime.jsx(StyledMenuItem, {
disabled: !canUpdate,
onSelect: toggleEditReleaseModal,
children: /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
alignItems: "center",
gap: 2,
hasRadius: true,
width: "100%",
children: [
/*#__PURE__*/ jsxRuntime.jsx(PencilIcon, {}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
ellipsis: true,
children: formatMessage({
id: 'content-releases.header.actions.edit',
defaultMessage: 'Edit'
})
})
]
})
}),
/*#__PURE__*/ jsxRuntime.jsx(StyledMenuItem, {
disabled: !canDelete,
onSelect: toggleWarningSubmit,
$variant: "danger",
children: /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
alignItems: "center",
gap: 2,
hasRadius: true,
width: "100%",
children: [
/*#__PURE__*/ jsxRuntime.jsx(TrashIcon, {}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
ellipsis: true,
textColor: "danger600",
children: formatMessage({
id: 'content-releases.header.actions.delete',
defaultMessage: 'Delete'
})
})
]
})
}),
/*#__PURE__*/ jsxRuntime.jsxs(ReleaseInfoWrapper, {
direction: "column",
justifyContent: "center",
alignItems: "flex-start",
gap: 1,
padding: 4,
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
variant: "pi",
fontWeight: "bold",
children: formatMessage({
id: 'content-releases.header.actions.created',
defaultMessage: 'Created'
})
}),
/*#__PURE__*/ jsxRuntime.jsxs(designSystem.Typography, {
variant: "pi",
color: "neutral300",
children: [
/*#__PURE__*/ jsxRuntime.jsx(RelativeTime.RelativeTime, {
timestamp: new Date(release$1.createdAt)
}),
formatMessage({
id: 'content-releases.header.actions.created.description',
defaultMessage: '{hasCreatedByUser, select, true { by {createdBy}} other { by deleted user}}'
}, {
createdBy: getCreatedByUser(),
hasCreatedByUser
})
]
})
]
})
]
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Button, {
size: "S",
variant: "tertiary",
onClick: handleRefresh,
children: formatMessage({
id: 'content-releases.header.actions.refresh',
defaultMessage: 'Refresh'
})
}),
canPublish ? /*#__PURE__*/ jsxRuntime.jsx(designSystem.Button, {
size: "S",
variant: "default",
onClick: handlePublishRelease(release$1.id.toString()),
loading: isPublishing,
disabled: release$1.actions.meta.count === 0,
children: formatMessage({
id: 'content-releases.header.actions.publish',
defaultMessage: 'Publish'
})
}) : null
]
})
}),
children
]
});
};
const SimpleMenuButton = styledComponents.styled(designSystem.SimpleMenu)`
& > span {
display: flex;
}
`;
/* -------------------------------------------------------------------------------------------------
* ReleaseDetailsBody
* -----------------------------------------------------------------------------------------------*/ const GROUP_BY_OPTIONS = [
'contentType',
'locale',
'action'
];
const GROUP_BY_OPTIONS_NO_LOCALE = [
'contentType',
'action'
];
const getGroupByOptionLabel = (value)=>{
if (value === 'locale') {
return {
id: 'content-releases.pages.ReleaseDetails.groupBy.option.locales',
defaultMessage: 'Locales'
};
}
if (value === 'action') {
return {
id: 'content-releases.pages.ReleaseDetails.groupBy.option.actions',
defaultMessage: 'Actions'
};
}
return {
id: 'content-releases.pages.ReleaseDetails.groupBy.option.content-type',
defaultMessage: 'Content-Types'
};
};
const ReleaseDetailsBody = ({ releaseId })=>{
const { formatMessage } = reactIntl.useIntl();
const [{ query }, setQuery] = strapiAdmin.useQueryParams();
const { toggleNotification } = strapiAdmin.useNotification();
const { formatAPIError } = strapiAdmin.useAPIErrorHandler();
const { data: releaseData, isLoading: isReleaseLoading, error: releaseError } = release.useGetReleaseQuery({
id: releaseId
});
const { allowedActions: { canUpdate } } = strapiAdmin.useRBAC(constants.PERMISSIONS);
const runHookWaterfall = strapiAdmin.useStrapiApp('ReleaseDetailsPage', (state)=>state.runHookWaterfall);
// TODO: Migrated displayedHeader to v5
const { displayedHeaders, hasI18nEnabled } = runHookWaterfall('ContentReleases/pages/ReleaseDetails/add-locale-in-releases', {
displayedHeaders: [
{
label: {
id: 'content-releases.page.ReleaseDetails.table.header.label.name',
defaultMessage: 'name'
},
name: 'name'
}
],
hasI18nEnabled: false
});
const release$1 = releaseData?.data;
const selectedGroupBy = query?.groupBy || 'contentType';
const { isLoading, isFetching, isError, data, error: releaseActionsError } = release.useGetReleaseActionsQuery({
...query,
releaseId
});
const [updateReleaseAction] = release.useUpdateReleaseActionMutation();
const handleChangeType = async (e, actionId, actionPath)=>{
const response = await updateReleaseAction({
params: {
releaseId,
actionId
},
body: {
type: e.target.value
},
query,
actionPath
});
if ('error' in response) {
if (strapiAdmin.isFetchError(response.error)) {
// When the response returns an object with 'error', handle fetch error
toggleNotification({
type: 'danger',
message: formatAPIError(response.error)
});
} else {
// Otherwise, the response returns an object with 'error', handle a generic error
toggleNotification({
type: 'danger',
message: formatMessage({
id: 'notification.error',
defaultMessage: 'An error occurred'
})
});
}
}
};
if (isLoading || isReleaseLoading) {
return /*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.Page.Loading, {});
}
const releaseActions = data?.data;
const releaseMeta = data?.meta;
const contentTypes = releaseMeta?.contentTypes || {};
releaseMeta?.components || {};
if (api.isBaseQueryError(releaseError) || !release$1) {
const errorsArray = [];
if (releaseError && 'code' in releaseError) {
errorsArray.push({
code: releaseError.code
});
}
if (releaseActionsError && 'code' in releaseActionsError) {
errorsArray.push({
code: releaseActionsError.code
});
}
return /*#__PURE__*/ jsxRuntime.jsx(reactRouterDom.Navigate, {
to: "..",
state: {
errors: errorsArray
}
});
}
if (isError || !releaseActions) {
return /*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.Page.Error, {});
}
if (Object.keys(releaseActions).length === 0) {
return /*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.Layouts.Content, {
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.EmptyStateLayout, {
action: /*#__PURE__*/ jsxRuntime.jsx(designSystem.LinkButton, {
tag: reactRouterDom.Link,
to: {
pathname: '/content-manager'
},
style: {
textDecoration: 'none'
},
variant: "secondary",
children: formatMessage({
id: 'content-releases.page.Details.button.openContentManager',
defaultMessage: 'Open the Content Manager'
})
}),
icon: /*#__PURE__*/ jsxRuntime.jsx(symbols.EmptyDocuments, {
width: "16rem"
}),
content: formatMessage({
id: 'content-releases.pages.Details.tab.emptyEntries',
defaultMessage: 'This release is empty. Open the Content Manager, select an entry and add it to the release.'
})
})
});
}
const groupByLabel = formatMessage({
id: 'content-releases.pages.ReleaseDetails.groupBy.aria-label',
defaultMessage: 'Group by'
});
const headers = [
...displayedHeaders,
{
label: {
id: 'content-releases.page.ReleaseDetails.table.header.label.content-type',
defaultMessage: 'content-type'
},
name: 'content-type'
},
{
label: {
id: 'content-releases.page.ReleaseDetails.table.header.label.action',
defaultMessage: 'action'
},
name: 'action'
},
...!release$1.releasedAt ? [
{
label: {
id: 'content-releases.page.ReleaseDetails.table.header.label.status',
defaultMessage: 'status'
},
name: 'status'
}
] : []
];
const options = hasI18nEnabled ? GROUP_BY_OPTIONS : GROUP_BY_OPTIONS_NO_LOCALE;
return /*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.Layouts.Content, {
children: /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
gap: 8,
direction: "column",
alignItems: "stretch",
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Flex, {
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.SingleSelect, {
placeholder: groupByLabel,
"aria-label": groupByLabel,
customizeContent: (value)=>formatMessage({
id: `content-releases.pages.ReleaseDetails.groupBy.label`,
defaultMessage: `Group by {groupBy}`
}, {
groupBy: value
}),
value: formatMessage(getGroupByOptionLabel(selectedGroupBy)),
onChange: (value)=>setQuery({
groupBy: value
}),
children: options.map((option)=>/*#__PURE__*/ jsxRuntime.jsx(designSystem.SingleSelectOption, {
value: option,
children: formatMessage(getGroupByOptionLabel(option))
}, option))
})
}),
Object.keys(releaseActions).map((key)=>/*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
gap: 4,
direction: "column",
alignItems: "stretch",
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Flex, {
role: "separator",
"aria-label": key,
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Badge, {
children: key
})
}),
/*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.Table.Root, {
rows: releaseActions[key].map((item)=>({
...item,
id: Number(item.entry.id)
})),
headers: headers,
isLoading: isLoading || isFetching,
children: /*#__PURE__*/ jsxRuntime.jsxs(strapiAdmin.Table.Content, {
children: [
/*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.Table.Head, {
children: headers.map(({ label, name })=>/*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.Table.HeaderCell, {
label: formatMessage(label),
name: name
}, name))
}),
/*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.Table.Loading, {}),
/*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.Table.Body, {
children: releaseActions[key].map(({ id, contentType, locale, type, entry, status }, actionIndex)=>/*#__PURE__*/ jsxRuntime.jsxs(designSystem.Tr, {
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Td, {
width: "25%",
maxWidth: "200px",
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
ellipsis: true,
children: `${contentType.mainFieldValue || entry.id}`
})
}),
hasI18nEnabled && /*#__PURE__*/ jsxRuntime.jsx(designSystem.Td, {
width: "10%",
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
children: `${locale?.name ? locale.name : '-'}`
})
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Td, {
width: "10%",
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
children: contentType.displayName || ''
})
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Td, {
width: "20%",
children: release$1.releasedAt ? /*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
children: formatMessage({
id: 'content-releases.page.ReleaseDetails.table.action-published',
defaultMessage: 'This entry was <b>{isPublish, select, true {published} other {unpublished}}</b>.'
}, {
isPublish: type === 'publish',
b: (children)=>/*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
fontWeight: "bold",
children: children
})
})
}) : /*#__PURE__*/ jsxRuntime.jsx(ReleaseActionOptions.ReleaseActionOptions, {
selected: type,
handleChange: (e)=>handleChangeType(e, id, [
key,
actionIndex
]),
name: `release-action-${id}-type`,
disabled: !canUpdate
})
}),
!release$1.releasedAt && /*#__PURE__*/ jsxRuntime.jsxs(jsxRuntime.Fragment, {
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Td, {
width: "20%",
minWidth: "200px",
children: /*#__PURE__*/ jsxRuntime.jsx(EntryValidationPopover.EntryValidationPopover, {
action: type,
schema: contentTypes?.[contentType.uid],
entry: entry,
status: status
})
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Td, {
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Flex, {
justifyContent: "flex-end",
children: /*#__PURE__*/ jsxRuntime.jsxs(ReleaseActionMenu.ReleaseActionMenu.Root, {
children: [
/*#__PURE__*/ jsxRuntime.jsx(ReleaseActionMenu.ReleaseActionMenu.ReleaseActionEntryLinkItem, {
contentTypeUid: contentType.uid,
documentId: entry.documentId,
locale: locale?.code
}),
/*#__PURE__*/ jsxRuntime.jsx(ReleaseActionMenu.ReleaseActionMenu.DeleteReleaseActionItem, {
releaseId: release$1.id,
actionId: id
})
]
})
})
})
]
})
]
}, id))
})
]
})
})
]
}, `releases-group-${key}`)),
/*#__PURE__*/ jsxRuntime.jsxs(strapiAdmin.Pagination.Root, {
...releaseMeta?.pagination,
defaultPageSize: releaseMeta?.pagination?.pageSize,
children: [
/*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.Pagination.PageSize, {}),
/*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.Pagination.Links, {})
]
})
]
})
});
};
/* -------------------------------------------------------------------------------------------------
* ReleaseDetailsPage
* -----------------------------------------------------------------------------------------------*/ const ReleaseDetailsPage = ()=>{
const { formatMessage } = reactIntl.useIntl();
const { releaseId } = reactRouterDom.useParams();
const { toggleNotification } = strapiAdmin.useNotification();
const { formatAPIError } = strapiAdmin.useAPIErrorHandler();
const navigate = reactRouterDom.useNavigate();
const [releaseModalShown, setReleaseModalShown] = React__namespace.useState(false);
const [showWarningSubmit, setWarningSubmit] = React__namespace.useState(false);
const { isLoading: isLoadingDetails, data, isSuccess: isSuccessDetails } = release.useGetReleaseQuery({
id: releaseId
}, {
skip: !releaseId
});
const { data: dataTimezone, isLoading: isLoadingTimezone } = release.useGetReleaseSettingsQuery();
const [updateRelease, { isLoading: isSubmittingForm }] = release.useUpdateReleaseMutation();
const [deleteRelease] = release.useDeleteReleaseMutation();
const toggleEditReleaseModal = ()=>{
setReleaseModalShown((prev)=>!prev);
};
const getTimezoneValue = ()=>{
if (releaseData?.timezone) {
return releaseData.timezone;
} else {
if (dataTimezone?.data.defaultTimezone) {
return dataTimezone.data.defaultTimezone;
}
return null;
}
};
const toggleWarningSubmit = ()=>setWarningSubmit((prevState)=>!prevState);
if (isLoadingDetails || isLoadingTimezone) {
return /*#__PURE__*/ jsxRuntime.jsx(ReleaseDetailsLayout, {
toggleEditReleaseModal: toggleEditReleaseModal,
toggleWarningSubmit: toggleWarningSubmit,
children: /*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.Page.Loading, {})
});
}
if (!releaseId) {
return /*#__PURE__*/ jsxRuntime.jsx(reactRouterDom.Navigate, {
to: ".."
});
}
const releaseData = isSuccessDetails && data?.data || null;
const title = releaseData?.name || '';
const timezone = getTimezoneValue();
const scheduledAt = releaseData?.scheduledAt && timezone ? dateFnsTz.utcToZonedTime(releaseData.scheduledAt, timezone) : null;
// Just get the date and time to display without considering updated timezone time
const date = scheduledAt ? format(scheduledAt, 'yyyy-MM-dd') : undefined;
const time = scheduledAt ? format(scheduledAt, 'HH:mm') : '';
const handleEditRelease = async (values)=>{
const response = await updateRelease({
id: releaseId,
name: values.name,
scheduledAt: values.scheduledAt,
timezone: values.timezone
});
if ('data' in response) {
// When the response returns an object with 'data', handle success
toggleNotification({
type: 'success',
message: formatMessage({
id: 'content-releases.modal.release-updated-notification-success',
defaultMessage: 'Release updated.'
})
});
toggleEditReleaseModal();
} else if (strapiAdmin.isFetchError(response.error)) {
// When the response returns an object with 'error', handle fetch error
toggleNotification({
type: 'danger',
message: formatAPIError(response.error)
});
} else {
// Otherwise, the response returns an object with 'error', handle a generic error
toggleNotification({
type: 'danger',
message: formatMessage({
id: 'notification.error',
defaultMessage: 'An error occurred'
})
});
}
};
const handleDeleteRelease = async ()=>{
const response = await deleteRelease({
id: releaseId
});
if ('data' in response) {
navigate('..');
} else if (strapiAdmin.isFetchError(response.error)) {
// When the response returns an object with 'error', handle fetch error
toggleNotification({
type: 'danger',
message: formatAPIError(response.error)
});
} else {
// Otherwise, the response returns an object with 'error', handle a generic error
toggleNotification({
type: 'danger',
message: formatMessage({
id: 'notification.error',
defaultMessage: 'An error occurred'
})
});
}
};
return /*#__PURE__*/ jsxRuntime.jsxs(ReleaseDetailsLayout, {
toggleEditReleaseModal: toggleEditReleaseModal,
toggleWarningSubmit: toggleWarningSubmit,
children: [
/*#__PURE__*/ jsxRuntime.jsx(ReleaseDetailsBody, {
releaseId: releaseId
}),
/*#__PURE__*/ jsxRuntime.jsx(ReleaseModal.ReleaseModal, {
open: releaseModalShown,
handleClose: toggleEditReleaseModal,
handleSubmit: handleEditRelease,
isLoading: isLoadingDetails || isSubmittingForm,
initialValues: {
name: title || '',
scheduledAt,
date,
time,
isScheduled: Boolean(scheduledAt),
timezone
}
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Dialog.Root, {
open: showWarningSubmit,
onOpenChange: toggleWarningSubmit,
children: /*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.ConfirmDialog, {
onConfirm: handleDeleteRelease,
children: formatMessage({
id: 'content-releases.dialog.confirmation-message',
defaultMessage: 'Are you sure you want to delete this release?'
})
})
})
]
});
};
exports.ReleaseDetailsPage = ReleaseDetailsPage;
//# sourceMappingURL=ReleaseDetailsPage.js.map
@@ -0,0 +1 @@
{"version":3,"file":"ReleaseDetailsPage.js","sources":["../../../admin/src/pages/ReleaseDetailsPage.tsx"],"sourcesContent":["import * as React from 'react';\n\nimport {\n Page,\n Pagination,\n Table,\n BackButton,\n ConfirmDialog,\n useTracking,\n useAPIErrorHandler,\n useNotification,\n useQueryParams,\n useRBAC,\n isFetchError,\n useStrapiApp,\n Layouts,\n} from '@strapi/admin/strapi-admin';\nimport {\n Button,\n Flex,\n Main,\n Tr,\n Td,\n Typography,\n Badge,\n SingleSelect,\n SingleSelectOption,\n EmptyStateLayout,\n LinkButton,\n Dialog,\n SimpleMenu,\n MenuItem,\n} from '@strapi/design-system';\nimport { More, Pencil, Trash } from '@strapi/icons';\nimport { EmptyDocuments } from '@strapi/icons/symbols';\nimport format from 'date-fns/format';\nimport { utcToZonedTime } from 'date-fns-tz';\nimport { useIntl } from 'react-intl';\nimport { useParams, useNavigate, Link as ReactRouterLink, Navigate } from 'react-router-dom';\nimport { styled } from 'styled-components';\n\nimport { EntryValidationPopover } from '../components/EntryValidationPopover';\nimport { RelativeTime } from '../components/RelativeTime';\nimport { ReleaseActionMenu } from '../components/ReleaseActionMenu';\nimport { ReleaseActionOptions } from '../components/ReleaseActionOptions';\nimport { ReleaseModal, FormValues } from '../components/ReleaseModal';\nimport { PERMISSIONS } from '../constants';\nimport {\n GetReleaseActionsQueryParams,\n useGetReleaseActionsQuery,\n useGetReleaseQuery,\n useGetReleaseSettingsQuery,\n useUpdateReleaseMutation,\n useUpdateReleaseActionMutation,\n usePublishReleaseMutation,\n useDeleteReleaseMutation,\n releaseApi,\n} from '../services/release';\nimport { useTypedDispatch } from '../store/hooks';\nimport { isBaseQueryError } from '../utils/api';\nimport { getTimezoneOffset } from '../utils/time';\n\nimport { getBadgeProps } from './ReleasesPage';\n\nimport type {\n ReleaseAction,\n ReleaseActionGroupBy,\n} from '../../../shared/contracts/release-actions';\n\n/* -------------------------------------------------------------------------------------------------\n * ReleaseDetailsLayout\n * -----------------------------------------------------------------------------------------------*/\nconst ReleaseInfoWrapper = styled(Flex)`\n align-self: stretch;\n border-bottom-right-radius: ${({ theme }) => theme.borderRadius};\n border-bottom-left-radius: ${({ theme }) => theme.borderRadius};\n border-top: 1px solid ${({ theme }) => theme.colors.neutral150};\n`;\n\nconst StyledMenuItem = styled(MenuItem)<{\n disabled?: boolean;\n $variant?: 'neutral' | 'danger';\n}>`\n svg path {\n fill: ${({ theme, disabled }) => disabled && theme.colors.neutral500};\n }\n span {\n color: ${({ theme, disabled }) => disabled && theme.colors.neutral500};\n }\n\n &:hover {\n background: ${({ theme, $variant = 'neutral' }) => theme.colors[`${$variant}100`]};\n }\n`;\n\nconst PencilIcon = styled(Pencil)`\n width: ${({ theme }) => theme.spaces[4]};\n height: ${({ theme }) => theme.spaces[4]};\n path {\n fill: ${({ theme }) => theme.colors.neutral600};\n }\n`;\n\nconst TrashIcon = styled(Trash)`\n width: ${({ theme }) => theme.spaces[4]};\n height: ${({ theme }) => theme.spaces[4]};\n path {\n fill: ${({ theme }) => theme.colors.danger600};\n }\n`;\n\ninterface ReleaseDetailsLayoutProps {\n toggleEditReleaseModal: () => void;\n toggleWarningSubmit: () => void;\n children: React.ReactNode;\n}\n\nconst ReleaseDetailsLayout = ({\n toggleEditReleaseModal,\n toggleWarningSubmit,\n children,\n}: ReleaseDetailsLayoutProps) => {\n const { formatMessage, formatDate, formatTime } = useIntl();\n const { releaseId } = useParams<{ releaseId: string }>();\n const {\n data,\n isLoading: isLoadingDetails,\n error,\n } = useGetReleaseQuery(\n { id: releaseId! },\n {\n skip: !releaseId,\n }\n );\n const [publishRelease, { isLoading: isPublishing }] = usePublishReleaseMutation();\n const { toggleNotification } = useNotification();\n const { formatAPIError } = useAPIErrorHandler();\n const { allowedActions } = useRBAC(PERMISSIONS);\n const { canUpdate, canDelete, canPublish } = allowedActions;\n const dispatch = useTypedDispatch();\n const { trackUsage } = useTracking();\n\n const release = data?.data;\n\n const handlePublishRelease = (id: string) => async () => {\n const response = await publishRelease({ id });\n\n if ('data' in response) {\n // When the response returns an object with 'data', handle success\n toggleNotification({\n type: 'success',\n message: formatMessage({\n id: 'content-releases.pages.ReleaseDetails.publish-notification-success',\n defaultMessage: 'Release was published successfully.',\n }),\n });\n\n const { totalEntries, totalPublishedEntries, totalUnpublishedEntries } = response.data.meta;\n\n trackUsage('didPublishRelease', {\n totalEntries,\n Line truncated
@@ -0,0 +1,800 @@
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
import * as React from 'react';
import { useNotification, useAPIErrorHandler, Page, ConfirmDialog, useRBAC, useTracking, Layouts, BackButton, useQueryParams, useStrapiApp, Table, Pagination, isFetchError } from '@strapi/admin/strapi-admin';
import { Flex, MenuItem, SimpleMenu, Dialog, Main, Typography, Badge, Button, EmptyStateLayout, LinkButton, SingleSelect, SingleSelectOption, Tr, Td } from '@strapi/design-system';
import { Pencil, Trash, More } from '@strapi/icons';
import { EmptyDocuments } from '@strapi/icons/symbols';
import format from 'date-fns/format';
import { utcToZonedTime } from 'date-fns-tz';
import { useIntl } from 'react-intl';
import { useParams, useNavigate, Navigate, Link } from 'react-router-dom';
import { styled } from 'styled-components';
import { EntryValidationPopover } from '../components/EntryValidationPopover.mjs';
import { RelativeTime } from '../components/RelativeTime.mjs';
import { ReleaseActionMenu } from '../components/ReleaseActionMenu.mjs';
import { ReleaseActionOptions } from '../components/ReleaseActionOptions.mjs';
import { ReleaseModal } from '../components/ReleaseModal.mjs';
import { PERMISSIONS } from '../constants.mjs';
import { useGetReleaseQuery, useGetReleaseSettingsQuery, useUpdateReleaseMutation, useDeleteReleaseMutation, usePublishReleaseMutation, useGetReleaseActionsQuery, useUpdateReleaseActionMutation, releaseApi } from '../services/release.mjs';
import { useTypedDispatch } from '../store/hooks.mjs';
import { isBaseQueryError } from '../utils/api.mjs';
import { getTimezoneOffset } from '../utils/time.mjs';
import { getBadgeProps } from './ReleasesPage.mjs';
/* -------------------------------------------------------------------------------------------------
* ReleaseDetailsLayout
* -----------------------------------------------------------------------------------------------*/ const ReleaseInfoWrapper = styled(Flex)`
align-self: stretch;
border-bottom-right-radius: ${({ theme })=>theme.borderRadius};
border-bottom-left-radius: ${({ theme })=>theme.borderRadius};
border-top: 1px solid ${({ theme })=>theme.colors.neutral150};
`;
const StyledMenuItem = styled(MenuItem)`
svg path {
fill: ${({ theme, disabled })=>disabled && theme.colors.neutral500};
}
span {
color: ${({ theme, disabled })=>disabled && theme.colors.neutral500};
}
&:hover {
background: ${({ theme, $variant = 'neutral' })=>theme.colors[`${$variant}100`]};
}
`;
const PencilIcon = styled(Pencil)`
width: ${({ theme })=>theme.spaces[4]};
height: ${({ theme })=>theme.spaces[4]};
path {
fill: ${({ theme })=>theme.colors.neutral600};
}
`;
const TrashIcon = styled(Trash)`
width: ${({ theme })=>theme.spaces[4]};
height: ${({ theme })=>theme.spaces[4]};
path {
fill: ${({ theme })=>theme.colors.danger600};
}
`;
const ReleaseDetailsLayout = ({ toggleEditReleaseModal, toggleWarningSubmit, children })=>{
const { formatMessage, formatDate, formatTime } = useIntl();
const { releaseId } = useParams();
const { data, isLoading: isLoadingDetails, error } = useGetReleaseQuery({
id: releaseId
}, {
skip: !releaseId
});
const [publishRelease, { isLoading: isPublishing }] = usePublishReleaseMutation();
const { toggleNotification } = useNotification();
const { formatAPIError } = useAPIErrorHandler();
const { allowedActions } = useRBAC(PERMISSIONS);
const { canUpdate, canDelete, canPublish } = allowedActions;
const dispatch = useTypedDispatch();
const { trackUsage } = useTracking();
const release = data?.data;
const handlePublishRelease = (id)=>async ()=>{
const response = await publishRelease({
id
});
if ('data' in response) {
// When the response returns an object with 'data', handle success
toggleNotification({
type: 'success',
message: formatMessage({
id: 'content-releases.pages.ReleaseDetails.publish-notification-success',
defaultMessage: 'Release was published successfully.'
})
});
const { totalEntries, totalPublishedEntries, totalUnpublishedEntries } = response.data.meta;
trackUsage('didPublishRelease', {
totalEntries,
totalPublishedEntries,
totalUnpublishedEntries
});
} else if (isFetchError(response.error)) {
// When the response returns an object with 'error', handle fetch error
toggleNotification({
type: 'danger',
message: formatAPIError(response.error)
});
} else {
// Otherwise, the response returns an object with 'error', handle a generic error
toggleNotification({
type: 'danger',
message: formatMessage({
id: 'notification.error',
defaultMessage: 'An error occurred'
})
});
}
};
const handleRefresh = ()=>{
dispatch(releaseApi.util.invalidateTags([
{
type: 'ReleaseAction',
id: 'LIST'
},
{
type: 'Release',
id: releaseId
}
]));
};
const getCreatedByUser = ()=>{
if (!release?.createdBy) {
return null;
}
// Favor the username
if (release.createdBy.username) {
return release.createdBy.username;
}
// Firstname may not exist if created with SSO
if (release.createdBy.firstname) {
return `${release.createdBy.firstname} ${release.createdBy.lastname || ''}`.trim();
}
// All users must have at least an email
return release.createdBy.email;
};
if (isLoadingDetails) {
return /*#__PURE__*/ jsx(Page.Loading, {});
}
if (isBaseQueryError(error) && 'code' in error || !release) {
return /*#__PURE__*/ jsx(Navigate, {
to: "..",
state: {
errors: [
{
// @ts-expect-error – TODO: fix this weird error flow
code: error?.code
}
]
}
});
}
const totalEntries = release.actions.meta.count || 0;
const hasCreatedByUser = Boolean(getCreatedByUser());
const isScheduled = release.scheduledAt && release.timezone;
const numberOfEntriesText = formatMessage({
id: 'content-releases.pages.Details.header-subtitle',
defaultMessage: '{number, plural, =0 {No entries} one {# entry} other {# entries}}'
}, {
number: totalEntries
});
const scheduledText = isScheduled ? formatMessage({
id: 'content-releases.pages.ReleaseDetails.header-subtitle.scheduled',
defaultMessage: 'Scheduled for {date} at {time} ({offset})'
}, {
date: formatDate(new Date(release.scheduledAt), {
weekday: 'long',
day: 'numeric',
month: 'long',
year: 'numeric',
timeZone: release.timezone
}),
time: formatTime(new Date(release.scheduledAt), {
timeZone: release.timezone,
hourCycle: 'h23'
}),
offset: getTimezoneOffset(release.timezone, new Date(release.scheduledAt))
}) : '';
return /*#__PURE__*/ jsxs(Main, {
"aria-busy": isLoadingDetails,
children: [
/*#__PURE__*/ jsx(Layouts.Header, {
title: release.name,
subtitle: /*#__PURE__*/ jsxs(Flex, {
gap: 2,
lineHeight: 6,
children: [
/*#__PURE__*/ jsx(Typography, {
textColor: "neutral600",
variant: "epsilon",
children: numberOfEntriesText + (isScheduled ? ` - ${scheduledText}` : '')
}),
/*#__PURE__*/ jsx(Badge, {
...getBadgeProps(release.status),
children: release.status
})
]
}),
navigationAction: /*#__PURE__*/ jsx(BackButton, {
fallback: ".."
}),
primaryAction: !release.releasedAt && /*#__PURE__*/ jsxs(Flex, {
gap: 2,
children: [
/*#__PURE__*/ jsxs(SimpleMenuButton, {
label: /*#__PURE__*/ jsx(More, {}),
variant: "tertiary",
endIcon: null,
paddingLeft: "7px",
paddingRight: "7px",
"aria-label": formatMessage({
id: 'content-releases.header.actions.open-release-actions',
defaultMessage: 'Release edit and delete menu'
}),
popoverPlacement: "bottom-end",
children: [
/*#__PURE__*/ jsx(StyledMenuItem, {
disabled: !canUpdate,
onSelect: toggleEditReleaseModal,
children: /*#__PURE__*/ jsxs(Flex, {
alignItems: "center",
gap: 2,
hasRadius: true,
width: "100%",
children: [
/*#__PURE__*/ jsx(PencilIcon, {}),
/*#__PURE__*/ jsx(Typography, {
ellipsis: true,
children: formatMessage({
id: 'content-releases.header.actions.edit',
defaultMessage: 'Edit'
})
})
]
})
}),
/*#__PURE__*/ jsx(StyledMenuItem, {
disabled: !canDelete,
onSelect: toggleWarningSubmit,
$variant: "danger",
children: /*#__PURE__*/ jsxs(Flex, {
alignItems: "center",
gap: 2,
hasRadius: true,
width: "100%",
children: [
/*#__PURE__*/ jsx(TrashIcon, {}),
/*#__PURE__*/ jsx(Typography, {
ellipsis: true,
textColor: "danger600",
children: formatMessage({
id: 'content-releases.header.actions.delete',
defaultMessage: 'Delete'
})
})
]
})
}),
/*#__PURE__*/ jsxs(ReleaseInfoWrapper, {
direction: "column",
justifyContent: "center",
alignItems: "flex-start",
gap: 1,
padding: 4,
children: [
/*#__PURE__*/ jsx(Typography, {
variant: "pi",
fontWeight: "bold",
children: formatMessage({
id: 'content-releases.header.actions.created',
defaultMessage: 'Created'
})
}),
/*#__PURE__*/ jsxs(Typography, {
variant: "pi",
color: "neutral300",
children: [
/*#__PURE__*/ jsx(RelativeTime, {
timestamp: new Date(release.createdAt)
}),
formatMessage({
id: 'content-releases.header.actions.created.description',
defaultMessage: '{hasCreatedByUser, select, true { by {createdBy}} other { by deleted user}}'
}, {
createdBy: getCreatedByUser(),
hasCreatedByUser
})
]
})
]
})
]
}),
/*#__PURE__*/ jsx(Button, {
size: "S",
variant: "tertiary",
onClick: handleRefresh,
children: formatMessage({
id: 'content-releases.header.actions.refresh',
defaultMessage: 'Refresh'
})
}),
canPublish ? /*#__PURE__*/ jsx(Button, {
size: "S",
variant: "default",
onClick: handlePublishRelease(release.id.toString()),
loading: isPublishing,
disabled: release.actions.meta.count === 0,
children: formatMessage({
id: 'content-releases.header.actions.publish',
defaultMessage: 'Publish'
})
}) : null
]
})
}),
children
]
});
};
const SimpleMenuButton = styled(SimpleMenu)`
& > span {
display: flex;
}
`;
/* -------------------------------------------------------------------------------------------------
* ReleaseDetailsBody
* -----------------------------------------------------------------------------------------------*/ const GROUP_BY_OPTIONS = [
'contentType',
'locale',
'action'
];
const GROUP_BY_OPTIONS_NO_LOCALE = [
'contentType',
'action'
];
const getGroupByOptionLabel = (value)=>{
if (value === 'locale') {
return {
id: 'content-releases.pages.ReleaseDetails.groupBy.option.locales',
defaultMessage: 'Locales'
};
}
if (value === 'action') {
return {
id: 'content-releases.pages.ReleaseDetails.groupBy.option.actions',
defaultMessage: 'Actions'
};
}
return {
id: 'content-releases.pages.ReleaseDetails.groupBy.option.content-type',
defaultMessage: 'Content-Types'
};
};
const ReleaseDetailsBody = ({ releaseId })=>{
const { formatMessage } = useIntl();
const [{ query }, setQuery] = useQueryParams();
const { toggleNotification } = useNotification();
const { formatAPIError } = useAPIErrorHandler();
const { data: releaseData, isLoading: isReleaseLoading, error: releaseError } = useGetReleaseQuery({
id: releaseId
});
const { allowedActions: { canUpdate } } = useRBAC(PERMISSIONS);
const runHookWaterfall = useStrapiApp('ReleaseDetailsPage', (state)=>state.runHookWaterfall);
// TODO: Migrated displayedHeader to v5
const { displayedHeaders, hasI18nEnabled } = runHookWaterfall('ContentReleases/pages/ReleaseDetails/add-locale-in-releases', {
displayedHeaders: [
{
label: {
id: 'content-releases.page.ReleaseDetails.table.header.label.name',
defaultMessage: 'name'
},
name: 'name'
}
],
hasI18nEnabled: false
});
const release = releaseData?.data;
const selectedGroupBy = query?.groupBy || 'contentType';
const { isLoading, isFetching, isError, data, error: releaseActionsError } = useGetReleaseActionsQuery({
...query,
releaseId
});
const [updateReleaseAction] = useUpdateReleaseActionMutation();
const handleChangeType = async (e, actionId, actionPath)=>{
const response = await updateReleaseAction({
params: {
releaseId,
actionId
},
body: {
type: e.target.value
},
query,
actionPath
});
if ('error' in response) {
if (isFetchError(response.error)) {
// When the response returns an object with 'error', handle fetch error
toggleNotification({
type: 'danger',
message: formatAPIError(response.error)
});
} else {
// Otherwise, the response returns an object with 'error', handle a generic error
toggleNotification({
type: 'danger',
message: formatMessage({
id: 'notification.error',
defaultMessage: 'An error occurred'
})
});
}
}
};
if (isLoading || isReleaseLoading) {
return /*#__PURE__*/ jsx(Page.Loading, {});
}
const releaseActions = data?.data;
const releaseMeta = data?.meta;
const contentTypes = releaseMeta?.contentTypes || {};
releaseMeta?.components || {};
if (isBaseQueryError(releaseError) || !release) {
const errorsArray = [];
if (releaseError && 'code' in releaseError) {
errorsArray.push({
code: releaseError.code
});
}
if (releaseActionsError && 'code' in releaseActionsError) {
errorsArray.push({
code: releaseActionsError.code
});
}
return /*#__PURE__*/ jsx(Navigate, {
to: "..",
state: {
errors: errorsArray
}
});
}
if (isError || !releaseActions) {
return /*#__PURE__*/ jsx(Page.Error, {});
}
if (Object.keys(releaseActions).length === 0) {
return /*#__PURE__*/ jsx(Layouts.Content, {
children: /*#__PURE__*/ jsx(EmptyStateLayout, {
action: /*#__PURE__*/ jsx(LinkButton, {
tag: Link,
to: {
pathname: '/content-manager'
},
style: {
textDecoration: 'none'
},
variant: "secondary",
children: formatMessage({
id: 'content-releases.page.Details.button.openContentManager',
defaultMessage: 'Open the Content Manager'
})
}),
icon: /*#__PURE__*/ jsx(EmptyDocuments, {
width: "16rem"
}),
content: formatMessage({
id: 'content-releases.pages.Details.tab.emptyEntries',
defaultMessage: 'This release is empty. Open the Content Manager, select an entry and add it to the release.'
})
})
});
}
const groupByLabel = formatMessage({
id: 'content-releases.pages.ReleaseDetails.groupBy.aria-label',
defaultMessage: 'Group by'
});
const headers = [
...displayedHeaders,
{
label: {
id: 'content-releases.page.ReleaseDetails.table.header.label.content-type',
defaultMessage: 'content-type'
},
name: 'content-type'
},
{
label: {
id: 'content-releases.page.ReleaseDetails.table.header.label.action',
defaultMessage: 'action'
},
name: 'action'
},
...!release.releasedAt ? [
{
label: {
id: 'content-releases.page.ReleaseDetails.table.header.label.status',
defaultMessage: 'status'
},
name: 'status'
}
] : []
];
const options = hasI18nEnabled ? GROUP_BY_OPTIONS : GROUP_BY_OPTIONS_NO_LOCALE;
return /*#__PURE__*/ jsx(Layouts.Content, {
children: /*#__PURE__*/ jsxs(Flex, {
gap: 8,
direction: "column",
alignItems: "stretch",
children: [
/*#__PURE__*/ jsx(Flex, {
children: /*#__PURE__*/ jsx(SingleSelect, {
placeholder: groupByLabel,
"aria-label": groupByLabel,
customizeContent: (value)=>formatMessage({
id: `content-releases.pages.ReleaseDetails.groupBy.label`,
defaultMessage: `Group by {groupBy}`
}, {
groupBy: value
}),
value: formatMessage(getGroupByOptionLabel(selectedGroupBy)),
onChange: (value)=>setQuery({
groupBy: value
}),
children: options.map((option)=>/*#__PURE__*/ jsx(SingleSelectOption, {
value: option,
children: formatMessage(getGroupByOptionLabel(option))
}, option))
})
}),
Object.keys(releaseActions).map((key)=>/*#__PURE__*/ jsxs(Flex, {
gap: 4,
direction: "column",
alignItems: "stretch",
children: [
/*#__PURE__*/ jsx(Flex, {
role: "separator",
"aria-label": key,
children: /*#__PURE__*/ jsx(Badge, {
children: key
})
}),
/*#__PURE__*/ jsx(Table.Root, {
rows: releaseActions[key].map((item)=>({
...item,
id: Number(item.entry.id)
})),
headers: headers,
isLoading: isLoading || isFetching,
children: /*#__PURE__*/ jsxs(Table.Content, {
children: [
/*#__PURE__*/ jsx(Table.Head, {
children: headers.map(({ label, name })=>/*#__PURE__*/ jsx(Table.HeaderCell, {
label: formatMessage(label),
name: name
}, name))
}),
/*#__PURE__*/ jsx(Table.Loading, {}),
/*#__PURE__*/ jsx(Table.Body, {
children: releaseActions[key].map(({ id, contentType, locale, type, entry, status }, actionIndex)=>/*#__PURE__*/ jsxs(Tr, {
children: [
/*#__PURE__*/ jsx(Td, {
width: "25%",
maxWidth: "200px",
children: /*#__PURE__*/ jsx(Typography, {
ellipsis: true,
children: `${contentType.mainFieldValue || entry.id}`
})
}),
hasI18nEnabled && /*#__PURE__*/ jsx(Td, {
width: "10%",
children: /*#__PURE__*/ jsx(Typography, {
children: `${locale?.name ? locale.name : '-'}`
})
}),
/*#__PURE__*/ jsx(Td, {
width: "10%",
children: /*#__PURE__*/ jsx(Typography, {
children: contentType.displayName || ''
})
}),
/*#__PURE__*/ jsx(Td, {
width: "20%",
children: release.releasedAt ? /*#__PURE__*/ jsx(Typography, {
children: formatMessage({
id: 'content-releases.page.ReleaseDetails.table.action-published',
defaultMessage: 'This entry was <b>{isPublish, select, true {published} other {unpublished}}</b>.'
}, {
isPublish: type === 'publish',
b: (children)=>/*#__PURE__*/ jsx(Typography, {
fontWeight: "bold",
children: children
})
})
}) : /*#__PURE__*/ jsx(ReleaseActionOptions, {
selected: type,
handleChange: (e)=>handleChangeType(e, id, [
key,
actionIndex
]),
name: `release-action-${id}-type`,
disabled: !canUpdate
})
}),
!release.releasedAt && /*#__PURE__*/ jsxs(Fragment, {
children: [
/*#__PURE__*/ jsx(Td, {
width: "20%",
minWidth: "200px",
children: /*#__PURE__*/ jsx(EntryValidationPopover, {
action: type,
schema: contentTypes?.[contentType.uid],
entry: entry,
status: status
})
}),
/*#__PURE__*/ jsx(Td, {
children: /*#__PURE__*/ jsx(Flex, {
justifyContent: "flex-end",
children: /*#__PURE__*/ jsxs(ReleaseActionMenu.Root, {
children: [
/*#__PURE__*/ jsx(ReleaseActionMenu.ReleaseActionEntryLinkItem, {
contentTypeUid: contentType.uid,
documentId: entry.documentId,
locale: locale?.code
}),
/*#__PURE__*/ jsx(ReleaseActionMenu.DeleteReleaseActionItem, {
releaseId: release.id,
actionId: id
})
]
})
})
})
]
})
]
}, id))
})
]
})
})
]
}, `releases-group-${key}`)),
/*#__PURE__*/ jsxs(Pagination.Root, {
...releaseMeta?.pagination,
defaultPageSize: releaseMeta?.pagination?.pageSize,
children: [
/*#__PURE__*/ jsx(Pagination.PageSize, {}),
/*#__PURE__*/ jsx(Pagination.Links, {})
]
})
]
})
});
};
/* -------------------------------------------------------------------------------------------------
* ReleaseDetailsPage
* -----------------------------------------------------------------------------------------------*/ const ReleaseDetailsPage = ()=>{
const { formatMessage } = useIntl();
const { releaseId } = useParams();
const { toggleNotification } = useNotification();
const { formatAPIError } = useAPIErrorHandler();
const navigate = useNavigate();
const [releaseModalShown, setReleaseModalShown] = React.useState(false);
const [showWarningSubmit, setWarningSubmit] = React.useState(false);
const { isLoading: isLoadingDetails, data, isSuccess: isSuccessDetails } = useGetReleaseQuery({
id: releaseId
}, {
skip: !releaseId
});
const { data: dataTimezone, isLoading: isLoadingTimezone } = useGetReleaseSettingsQuery();
const [updateRelease, { isLoading: isSubmittingForm }] = useUpdateReleaseMutation();
const [deleteRelease] = useDeleteReleaseMutation();
const toggleEditReleaseModal = ()=>{
setReleaseModalShown((prev)=>!prev);
};
const getTimezoneValue = ()=>{
if (releaseData?.timezone) {
return releaseData.timezone;
} else {
if (dataTimezone?.data.defaultTimezone) {
return dataTimezone.data.defaultTimezone;
}
return null;
}
};
const toggleWarningSubmit = ()=>setWarningSubmit((prevState)=>!prevState);
if (isLoadingDetails || isLoadingTimezone) {
return /*#__PURE__*/ jsx(ReleaseDetailsLayout, {
toggleEditReleaseModal: toggleEditReleaseModal,
toggleWarningSubmit: toggleWarningSubmit,
children: /*#__PURE__*/ jsx(Page.Loading, {})
});
}
if (!releaseId) {
return /*#__PURE__*/ jsx(Navigate, {
to: ".."
});
}
const releaseData = isSuccessDetails && data?.data || null;
const title = releaseData?.name || '';
const timezone = getTimezoneValue();
const scheduledAt = releaseData?.scheduledAt && timezone ? utcToZonedTime(releaseData.scheduledAt, timezone) : null;
// Just get the date and time to display without considering updated timezone time
const date = scheduledAt ? format(scheduledAt, 'yyyy-MM-dd') : undefined;
const time = scheduledAt ? format(scheduledAt, 'HH:mm') : '';
const handleEditRelease = async (values)=>{
const response = await updateRelease({
id: releaseId,
name: values.name,
scheduledAt: values.scheduledAt,
timezone: values.timezone
});
if ('data' in response) {
// When the response returns an object with 'data', handle success
toggleNotification({
type: 'success',
message: formatMessage({
id: 'content-releases.modal.release-updated-notification-success',
defaultMessage: 'Release updated.'
})
});
toggleEditReleaseModal();
} else if (isFetchError(response.error)) {
// When the response returns an object with 'error', handle fetch error
toggleNotification({
type: 'danger',
message: formatAPIError(response.error)
});
} else {
// Otherwise, the response returns an object with 'error', handle a generic error
toggleNotification({
type: 'danger',
message: formatMessage({
id: 'notification.error',
defaultMessage: 'An error occurred'
})
});
}
};
const handleDeleteRelease = async ()=>{
const response = await deleteRelease({
id: releaseId
});
if ('data' in response) {
navigate('..');
} else if (isFetchError(response.error)) {
// When the response returns an object with 'error', handle fetch error
toggleNotification({
type: 'danger',
message: formatAPIError(response.error)
});
} else {
// Otherwise, the response returns an object with 'error', handle a generic error
toggleNotification({
type: 'danger',
message: formatMessage({
id: 'notification.error',
defaultMessage: 'An error occurred'
})
});
}
};
return /*#__PURE__*/ jsxs(ReleaseDetailsLayout, {
toggleEditReleaseModal: toggleEditReleaseModal,
toggleWarningSubmit: toggleWarningSubmit,
children: [
/*#__PURE__*/ jsx(ReleaseDetailsBody, {
releaseId: releaseId
}),
/*#__PURE__*/ jsx(ReleaseModal, {
open: releaseModalShown,
handleClose: toggleEditReleaseModal,
handleSubmit: handleEditRelease,
isLoading: isLoadingDetails || isSubmittingForm,
initialValues: {
name: title || '',
scheduledAt,
date,
time,
isScheduled: Boolean(scheduledAt),
timezone
}
}),
/*#__PURE__*/ jsx(Dialog.Root, {
open: showWarningSubmit,
onOpenChange: toggleWarningSubmit,
children: /*#__PURE__*/ jsx(ConfirmDialog, {
onConfirm: handleDeleteRelease,
children: formatMessage({
id: 'content-releases.dialog.confirmation-message',
defaultMessage: 'Are you sure you want to delete this release?'
})
})
})
]
});
};
export { ReleaseDetailsPage };
//# sourceMappingURL=ReleaseDetailsPage.mjs.map
@@ -0,0 +1 @@
{"version":3,"file":"ReleaseDetailsPage.mjs","sources":["../../../admin/src/pages/ReleaseDetailsPage.tsx"],"sourcesContent":["import * as React from 'react';\n\nimport {\n Page,\n Pagination,\n Table,\n BackButton,\n ConfirmDialog,\n useTracking,\n useAPIErrorHandler,\n useNotification,\n useQueryParams,\n useRBAC,\n isFetchError,\n useStrapiApp,\n Layouts,\n} from '@strapi/admin/strapi-admin';\nimport {\n Button,\n Flex,\n Main,\n Tr,\n Td,\n Typography,\n Badge,\n SingleSelect,\n SingleSelectOption,\n EmptyStateLayout,\n LinkButton,\n Dialog,\n SimpleMenu,\n MenuItem,\n} from '@strapi/design-system';\nimport { More, Pencil, Trash } from '@strapi/icons';\nimport { EmptyDocuments } from '@strapi/icons/symbols';\nimport format from 'date-fns/format';\nimport { utcToZonedTime } from 'date-fns-tz';\nimport { useIntl } from 'react-intl';\nimport { useParams, useNavigate, Link as ReactRouterLink, Navigate } from 'react-router-dom';\nimport { styled } from 'styled-components';\n\nimport { EntryValidationPopover } from '../components/EntryValidationPopover';\nimport { RelativeTime } from '../components/RelativeTime';\nimport { ReleaseActionMenu } from '../components/ReleaseActionMenu';\nimport { ReleaseActionOptions } from '../components/ReleaseActionOptions';\nimport { ReleaseModal, FormValues } from '../components/ReleaseModal';\nimport { PERMISSIONS } from '../constants';\nimport {\n GetReleaseActionsQueryParams,\n useGetReleaseActionsQuery,\n useGetReleaseQuery,\n useGetReleaseSettingsQuery,\n useUpdateReleaseMutation,\n useUpdateReleaseActionMutation,\n usePublishReleaseMutation,\n useDeleteReleaseMutation,\n releaseApi,\n} from '../services/release';\nimport { useTypedDispatch } from '../store/hooks';\nimport { isBaseQueryError } from '../utils/api';\nimport { getTimezoneOffset } from '../utils/time';\n\nimport { getBadgeProps } from './ReleasesPage';\n\nimport type {\n ReleaseAction,\n ReleaseActionGroupBy,\n} from '../../../shared/contracts/release-actions';\n\n/* -------------------------------------------------------------------------------------------------\n * ReleaseDetailsLayout\n * -----------------------------------------------------------------------------------------------*/\nconst ReleaseInfoWrapper = styled(Flex)`\n align-self: stretch;\n border-bottom-right-radius: ${({ theme }) => theme.borderRadius};\n border-bottom-left-radius: ${({ theme }) => theme.borderRadius};\n border-top: 1px solid ${({ theme }) => theme.colors.neutral150};\n`;\n\nconst StyledMenuItem = styled(MenuItem)<{\n disabled?: boolean;\n $variant?: 'neutral' | 'danger';\n}>`\n svg path {\n fill: ${({ theme, disabled }) => disabled && theme.colors.neutral500};\n }\n span {\n color: ${({ theme, disabled }) => disabled && theme.colors.neutral500};\n }\n\n &:hover {\n background: ${({ theme, $variant = 'neutral' }) => theme.colors[`${$variant}100`]};\n }\n`;\n\nconst PencilIcon = styled(Pencil)`\n width: ${({ theme }) => theme.spaces[4]};\n height: ${({ theme }) => theme.spaces[4]};\n path {\n fill: ${({ theme }) => theme.colors.neutral600};\n }\n`;\n\nconst TrashIcon = styled(Trash)`\n width: ${({ theme }) => theme.spaces[4]};\n height: ${({ theme }) => theme.spaces[4]};\n path {\n fill: ${({ theme }) => theme.colors.danger600};\n }\n`;\n\ninterface ReleaseDetailsLayoutProps {\n toggleEditReleaseModal: () => void;\n toggleWarningSubmit: () => void;\n children: React.ReactNode;\n}\n\nconst ReleaseDetailsLayout = ({\n toggleEditReleaseModal,\n toggleWarningSubmit,\n children,\n}: ReleaseDetailsLayoutProps) => {\n const { formatMessage, formatDate, formatTime } = useIntl();\n const { releaseId } = useParams<{ releaseId: string }>();\n const {\n data,\n isLoading: isLoadingDetails,\n error,\n } = useGetReleaseQuery(\n { id: releaseId! },\n {\n skip: !releaseId,\n }\n );\n const [publishRelease, { isLoading: isPublishing }] = usePublishReleaseMutation();\n const { toggleNotification } = useNotification();\n const { formatAPIError } = useAPIErrorHandler();\n const { allowedActions } = useRBAC(PERMISSIONS);\n const { canUpdate, canDelete, canPublish } = allowedActions;\n const dispatch = useTypedDispatch();\n const { trackUsage } = useTracking();\n\n const release = data?.data;\n\n const handlePublishRelease = (id: string) => async () => {\n const response = await publishRelease({ id });\n\n if ('data' in response) {\n // When the response returns an object with 'data', handle success\n toggleNotification({\n type: 'success',\n message: formatMessage({\n id: 'content-releases.pages.ReleaseDetails.publish-notification-success',\n defaultMessage: 'Release was published successfully.',\n }),\n });\n\n const { totalEntries, totalPublishedEntries, totalUnpublishedEntries } = response.data.meta;\n\n trackUsage('didPublishRelease', {\n totalEntries,\n Line truncated
@@ -0,0 +1,397 @@
'use strict';
var jsxRuntime = require('react/jsx-runtime');
var React = require('react');
var strapiAdmin = require('@strapi/admin/strapi-admin');
var ee = require('@strapi/admin/strapi-admin/ee');
var designSystem = require('@strapi/design-system');
var icons = require('@strapi/icons');
var symbols = require('@strapi/icons/symbols');
var dateFns = require('date-fns');
var reactIntl = require('react-intl');
var reactRouterDom = require('react-router-dom');
var styledComponents = require('styled-components');
var RelativeTime$1 = require('../components/RelativeTime.js');
var ReleaseModal = require('../components/ReleaseModal.js');
var constants = require('../constants.js');
var release = require('../services/release.js');
function _interopNamespaceDefault(e) {
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n.default = e;
return Object.freeze(n);
}
var React__namespace = /*#__PURE__*/_interopNamespaceDefault(React);
const LinkCard = styledComponents.styled(designSystem.Link)`
display: block;
`;
const RelativeTime = styledComponents.styled(RelativeTime$1.RelativeTime)`
display: inline-block;
&::first-letter {
text-transform: uppercase;
}
`;
const getBadgeProps = (status)=>{
let color;
switch(status){
case 'ready':
color = 'success';
break;
case 'blocked':
color = 'warning';
break;
case 'failed':
color = 'danger';
break;
case 'done':
color = 'primary';
break;
case 'empty':
default:
color = 'neutral';
}
return {
textColor: `${color}600`,
backgroundColor: `${color}100`,
borderColor: `${color}200`
};
};
const ReleasesGrid = ({ sectionTitle, releases = [], isError = false })=>{
const { formatMessage } = reactIntl.useIntl();
if (isError) {
return /*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.Page.Error, {});
}
if (releases?.length === 0) {
return /*#__PURE__*/ jsxRuntime.jsx(designSystem.EmptyStateLayout, {
content: formatMessage({
id: 'content-releases.page.Releases.tab.emptyEntries',
defaultMessage: 'No releases'
}, {
target: sectionTitle
}),
icon: /*#__PURE__*/ jsxRuntime.jsx(symbols.EmptyDocuments, {
width: "16rem"
})
});
}
return /*#__PURE__*/ jsxRuntime.jsx(designSystem.Grid.Root, {
gap: 4,
children: releases.map(({ id, name, scheduledAt, status })=>/*#__PURE__*/ jsxRuntime.jsx(designSystem.Grid.Item, {
col: 3,
s: 6,
xs: 12,
direction: "column",
alignItems: "stretch",
children: /*#__PURE__*/ jsxRuntime.jsx(LinkCard, {
tag: reactRouterDom.NavLink,
to: `${id}`,
isExternal: false,
children: /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
direction: "column",
justifyContent: "space-between",
padding: 4,
hasRadius: true,
background: "neutral0",
shadow: "tableShadow",
height: "100%",
width: "100%",
alignItems: "start",
gap: 4,
children: [
/*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
direction: "column",
alignItems: "start",
gap: 1,
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
textColor: "neutral800",
tag: "h3",
variant: "delta",
fontWeight: "bold",
children: name
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
variant: "pi",
textColor: "neutral600",
children: scheduledAt ? /*#__PURE__*/ jsxRuntime.jsx(RelativeTime, {
timestamp: new Date(scheduledAt)
}) : formatMessage({
id: 'content-releases.pages.Releases.not-scheduled',
defaultMessage: 'Not scheduled'
})
})
]
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Badge, {
...getBadgeProps(status),
children: status
})
]
})
})
}, id))
});
};
/* -------------------------------------------------------------------------------------------------
* ReleasesPage
* -----------------------------------------------------------------------------------------------*/ const StyledAlert = styledComponents.styled(designSystem.Alert)`
button {
display: none;
}
p + div {
margin-left: auto;
}
`;
const INITIAL_FORM_VALUES = {
name: '',
date: dateFns.format(new Date(), 'yyyy-MM-dd'),
time: '',
isScheduled: true,
scheduledAt: null,
timezone: null
};
const ReleasesPage = ()=>{
const location = reactRouterDom.useLocation();
const [releaseModalShown, setReleaseModalShown] = React__namespace.useState(false);
const { toggleNotification } = strapiAdmin.useNotification();
const { formatMessage } = reactIntl.useIntl();
const navigate = reactRouterDom.useNavigate();
const { formatAPIError } = strapiAdmin.useAPIErrorHandler();
const [{ query }, setQuery] = strapiAdmin.useQueryParams();
const response = release.useGetReleasesQuery(query);
const { data, isLoading: isLoadingSettings } = release.useGetReleaseSettingsQuery();
const [createRelease, { isLoading: isSubmittingForm }] = release.useCreateReleaseMutation();
const { getFeature } = ee.useLicenseLimits();
const { maximumReleases = 3 } = getFeature('cms-content-releases');
const { trackUsage } = strapiAdmin.useTracking();
const { allowedActions: { canCreate } } = strapiAdmin.useRBAC(constants.PERMISSIONS);
const { isLoading: isLoadingReleases, isSuccess, isError } = response;
const activeTab = response?.currentData?.meta?.activeTab || 'pending';
// Check if we have some errors and show a notification to the user to explain the error
React__namespace.useEffect(()=>{
if (location?.state?.errors) {
toggleNotification({
type: 'danger',
title: formatMessage({
id: 'content-releases.pages.Releases.notification.error.title',
defaultMessage: 'Your request could not be processed.'
}),
message: formatMessage({
id: 'content-releases.pages.Releases.notification.error.message',
defaultMessage: 'Please try again or open another release.'
})
});
navigate('', {
replace: true,
state: null
});
}
}, [
formatMessage,
location?.state?.errors,
navigate,
toggleNotification
]);
const toggleAddReleaseModal = ()=>{
setReleaseModalShown((prev)=>!prev);
};
if (isLoadingReleases || isLoadingSettings) {
return /*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.Page.Loading, {});
}
const totalPendingReleases = isSuccess && response.currentData?.meta?.pendingReleasesCount || 0;
const hasReachedMaximumPendingReleases = totalPendingReleases >= maximumReleases;
const handleTabChange = (tabValue)=>{
setQuery({
...query,
page: 1,
pageSize: response?.currentData?.meta?.pagination?.pageSize || 16,
filters: {
releasedAt: {
$notNull: tabValue !== 'pending'
}
}
});
};
const handleAddRelease = async ({ name, scheduledAt, timezone })=>{
const response = await createRelease({
name,
scheduledAt,
timezone
});
if ('data' in response) {
// When the response returns an object with 'data', handle success
toggleNotification({
type: 'success',
message: formatMessage({
id: 'content-releases.modal.release-created-notification-success',
defaultMessage: 'Release created.'
})
});
trackUsage('didCreateRelease');
navigate(response.data.data.id.toString());
} else if (strapiAdmin.isFetchError(response.error)) {
// When the response returns an object with 'error', handle fetch error
toggleNotification({
type: 'danger',
message: formatAPIError(response.error)
});
} else {
// Otherwise, the response returns an object with 'error', handle a generic error
toggleNotification({
type: 'danger',
message: formatMessage({
id: 'notification.error',
defaultMessage: 'An error occurred'
})
});
}
};
return /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Main, {
"aria-busy": isLoadingReleases || isLoadingSettings,
children: [
/*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.Layouts.Header, {
title: formatMessage({
id: 'content-releases.pages.Releases.title',
defaultMessage: 'Releases'
}),
subtitle: formatMessage({
id: 'content-releases.pages.Releases.header-subtitle',
defaultMessage: 'Create and manage content updates'
}),
primaryAction: canCreate ? /*#__PURE__*/ jsxRuntime.jsx(designSystem.Button, {
startIcon: /*#__PURE__*/ jsxRuntime.jsx(icons.Plus, {}),
onClick: toggleAddReleaseModal,
disabled: hasReachedMaximumPendingReleases,
children: formatMessage({
id: 'content-releases.header.actions.add-release',
defaultMessage: 'New release'
})
}) : null
}),
/*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.Layouts.Content, {
children: /*#__PURE__*/ jsxRuntime.jsxs(jsxRuntime.Fragment, {
children: [
hasReachedMaximumPendingReleases && /*#__PURE__*/ jsxRuntime.jsx(StyledAlert, {
marginBottom: 6,
action: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Link, {
href: "https://strapi.io/pricing-cloud",
isExternal: true,
children: formatMessage({
id: 'content-releases.pages.Releases.max-limit-reached.action',
defaultMessage: 'Explore plans'
})
}),
title: formatMessage({
id: 'content-releases.pages.Releases.max-limit-reached.title',
defaultMessage: 'You have reached the {number} pending {number, plural, one {release} other {releases}} limit.'
}, {
number: maximumReleases
}),
onClose: ()=>{},
closeLabel: "",
children: formatMessage({
id: 'content-releases.pages.Releases.max-limit-reached.message',
defaultMessage: 'Upgrade to manage an unlimited number of releases.'
})
}),
/*#__PURE__*/ jsxRuntime.jsxs(designSystem.Tabs.Root, {
variant: "simple",
onValueChange: handleTabChange,
value: activeTab,
children: [
/*#__PURE__*/ jsxRuntime.jsxs(designSystem.Box, {
paddingBottom: 8,
children: [
/*#__PURE__*/ jsxRuntime.jsxs(designSystem.Tabs.List, {
"aria-label": formatMessage({
id: 'content-releases.pages.Releases.tab-group.label',
defaultMessage: 'Releases list'
}),
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Tabs.Trigger, {
value: "pending",
children: formatMessage({
id: 'content-releases.pages.Releases.tab.pending',
defaultMessage: 'Pending ({count})'
}, {
count: totalPendingReleases
})
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Tabs.Trigger, {
value: "done",
children: formatMessage({
id: 'content-releases.pages.Releases.tab.done',
defaultMessage: 'Done'
})
})
]
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Divider, {})
]
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Tabs.Content, {
value: "pending",
children: /*#__PURE__*/ jsxRuntime.jsx(ReleasesGrid, {
sectionTitle: "pending",
releases: response?.currentData?.data,
isError: isError
})
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Tabs.Content, {
value: "done",
children: /*#__PURE__*/ jsxRuntime.jsx(ReleasesGrid, {
sectionTitle: "done",
releases: response?.currentData?.data,
isError: isError
})
})
]
}),
/*#__PURE__*/ jsxRuntime.jsxs(strapiAdmin.Pagination.Root, {
...response?.currentData?.meta?.pagination,
defaultPageSize: response?.currentData?.meta?.pagination?.pageSize,
children: [
/*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.Pagination.PageSize, {
options: [
'8',
'16',
'32',
'64'
]
}),
/*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.Pagination.Links, {})
]
})
]
})
}),
/*#__PURE__*/ jsxRuntime.jsx(ReleaseModal.ReleaseModal, {
open: releaseModalShown,
handleClose: toggleAddReleaseModal,
handleSubmit: handleAddRelease,
isLoading: isSubmittingForm,
initialValues: {
...INITIAL_FORM_VALUES,
timezone: data?.data.defaultTimezone ? data.data.defaultTimezone.split('&')[1] : null
}
})
]
});
};
exports.ReleasesPage = ReleasesPage;
exports.getBadgeProps = getBadgeProps;
//# sourceMappingURL=ReleasesPage.js.map
@@ -0,0 +1 @@
{"version":3,"file":"ReleasesPage.js","sources":["../../../admin/src/pages/ReleasesPage.tsx"],"sourcesContent":["import * as React from 'react';\n\nimport {\n Page,\n Pagination,\n useTracking,\n useAPIErrorHandler,\n useNotification,\n useQueryParams,\n useRBAC,\n isFetchError,\n Layouts,\n} from '@strapi/admin/strapi-admin';\nimport { useLicenseLimits } from '@strapi/admin/strapi-admin/ee';\nimport {\n Alert,\n Badge,\n Box,\n Button,\n Divider,\n EmptyStateLayout,\n Flex,\n Grid,\n Main,\n Tabs,\n Typography,\n Link,\n} from '@strapi/design-system';\nimport { Plus } from '@strapi/icons';\nimport { EmptyDocuments } from '@strapi/icons/symbols';\nimport { format } from 'date-fns';\nimport { useIntl } from 'react-intl';\nimport { useNavigate, useLocation, NavLink } from 'react-router-dom';\nimport { styled } from 'styled-components';\n\nimport { GetReleases, type Release } from '../../../shared/contracts/releases';\nimport { RelativeTime as BaseRelativeTime } from '../components/RelativeTime';\nimport { ReleaseModal, FormValues } from '../components/ReleaseModal';\nimport { PERMISSIONS } from '../constants';\nimport {\n useGetReleasesQuery,\n useGetReleaseSettingsQuery,\n GetReleasesQueryParams,\n useCreateReleaseMutation,\n} from '../services/release';\n\n/* -------------------------------------------------------------------------------------------------\n * ReleasesGrid\n * -----------------------------------------------------------------------------------------------*/\ninterface ReleasesGridProps {\n sectionTitle: 'pending' | 'done';\n releases?: GetReleases.Response['data'];\n isError?: boolean;\n}\n\nconst LinkCard = styled(Link)`\n display: block;\n`;\n\nconst RelativeTime = styled(BaseRelativeTime)`\n display: inline-block;\n &::first-letter {\n text-transform: uppercase;\n }\n`;\n\nconst getBadgeProps = (status: Release['status']) => {\n let color;\n switch (status) {\n case 'ready':\n color = 'success';\n break;\n case 'blocked':\n color = 'warning';\n break;\n case 'failed':\n color = 'danger';\n break;\n case 'done':\n color = 'primary';\n break;\n case 'empty':\n default:\n color = 'neutral';\n }\n\n return {\n textColor: `${color}600`,\n backgroundColor: `${color}100`,\n borderColor: `${color}200`,\n };\n};\n\nconst ReleasesGrid = ({ sectionTitle, releases = [], isError = false }: ReleasesGridProps) => {\n const { formatMessage } = useIntl();\n\n if (isError) {\n return <Page.Error />;\n }\n\n if (releases?.length === 0) {\n return (\n <EmptyStateLayout\n content={formatMessage(\n {\n id: 'content-releases.page.Releases.tab.emptyEntries',\n defaultMessage: 'No releases',\n },\n {\n target: sectionTitle,\n }\n )}\n icon={<EmptyDocuments width=\"16rem\" />}\n />\n );\n }\n\n return (\n <Grid.Root gap={4}>\n {releases.map(({ id, name, scheduledAt, status }) => (\n <Grid.Item col={3} s={6} xs={12} key={id} direction=\"column\" alignItems=\"stretch\">\n <LinkCard tag={NavLink} to={`${id}`} isExternal={false}>\n <Flex\n direction=\"column\"\n justifyContent=\"space-between\"\n padding={4}\n hasRadius\n background=\"neutral0\"\n shadow=\"tableShadow\"\n height=\"100%\"\n width=\"100%\"\n alignItems=\"start\"\n gap={4}\n >\n <Flex direction=\"column\" alignItems=\"start\" gap={1}>\n <Typography textColor=\"neutral800\" tag=\"h3\" variant=\"delta\" fontWeight=\"bold\">\n {name}\n </Typography>\n <Typography variant=\"pi\" textColor=\"neutral600\">\n {scheduledAt ? (\n <RelativeTime timestamp={new Date(scheduledAt)} />\n ) : (\n formatMessage({\n id: 'content-releases.pages.Releases.not-scheduled',\n defaultMessage: 'Not scheduled',\n })\n )}\n </Typography>\n </Flex>\n <Badge {...getBadgeProps(status)}>{status}</Badge>\n </Flex>\n </LinkCard>\n </Grid.Item>\n ))}\n </Grid.Root>\n );\n};\n\n/* -------------------------------------------------------------------------------------------------\n * ReleasesPage\n * -----------------------------------------------------------------------------------------------*/\n\nconst StyledAlert = styled(Alert)`\n button {\n display: none;\n }\n p + div {\n margin-left: auto;\n }\n`;\n\nconst INITIAL_FORM_VALUES = {\n name: '',\n date: format(new Date(), 'yyyy-MM-dd'),\n time: '',\n isScheduled: true,\n scheduledAt: null,\n timezone: null,\n} satisfies FormValues;Line truncated
@@ -0,0 +1,375 @@
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
import * as React from 'react';
import { useNotification, useAPIErrorHandler, useQueryParams, useTracking, useRBAC, Page, Layouts, Pagination, isFetchError } from '@strapi/admin/strapi-admin';
import { useLicenseLimits } from '@strapi/admin/strapi-admin/ee';
import { Link, Alert, Main, Button, Tabs, Box, Divider, EmptyStateLayout, Grid, Flex, Typography, Badge } from '@strapi/design-system';
import { Plus } from '@strapi/icons';
import { EmptyDocuments } from '@strapi/icons/symbols';
import { format } from 'date-fns';
import { useIntl } from 'react-intl';
import { useLocation, useNavigate, NavLink } from 'react-router-dom';
import { styled } from 'styled-components';
import { RelativeTime as RelativeTime$1 } from '../components/RelativeTime.mjs';
import { ReleaseModal } from '../components/ReleaseModal.mjs';
import { PERMISSIONS } from '../constants.mjs';
import { useGetReleasesQuery, useGetReleaseSettingsQuery, useCreateReleaseMutation } from '../services/release.mjs';
const LinkCard = styled(Link)`
display: block;
`;
const RelativeTime = styled(RelativeTime$1)`
display: inline-block;
&::first-letter {
text-transform: uppercase;
}
`;
const getBadgeProps = (status)=>{
let color;
switch(status){
case 'ready':
color = 'success';
break;
case 'blocked':
color = 'warning';
break;
case 'failed':
color = 'danger';
break;
case 'done':
color = 'primary';
break;
case 'empty':
default:
color = 'neutral';
}
return {
textColor: `${color}600`,
backgroundColor: `${color}100`,
borderColor: `${color}200`
};
};
const ReleasesGrid = ({ sectionTitle, releases = [], isError = false })=>{
const { formatMessage } = useIntl();
if (isError) {
return /*#__PURE__*/ jsx(Page.Error, {});
}
if (releases?.length === 0) {
return /*#__PURE__*/ jsx(EmptyStateLayout, {
content: formatMessage({
id: 'content-releases.page.Releases.tab.emptyEntries',
defaultMessage: 'No releases'
}, {
target: sectionTitle
}),
icon: /*#__PURE__*/ jsx(EmptyDocuments, {
width: "16rem"
})
});
}
return /*#__PURE__*/ jsx(Grid.Root, {
gap: 4,
children: releases.map(({ id, name, scheduledAt, status })=>/*#__PURE__*/ jsx(Grid.Item, {
col: 3,
s: 6,
xs: 12,
direction: "column",
alignItems: "stretch",
children: /*#__PURE__*/ jsx(LinkCard, {
tag: NavLink,
to: `${id}`,
isExternal: false,
children: /*#__PURE__*/ jsxs(Flex, {
direction: "column",
justifyContent: "space-between",
padding: 4,
hasRadius: true,
background: "neutral0",
shadow: "tableShadow",
height: "100%",
width: "100%",
alignItems: "start",
gap: 4,
children: [
/*#__PURE__*/ jsxs(Flex, {
direction: "column",
alignItems: "start",
gap: 1,
children: [
/*#__PURE__*/ jsx(Typography, {
textColor: "neutral800",
tag: "h3",
variant: "delta",
fontWeight: "bold",
children: name
}),
/*#__PURE__*/ jsx(Typography, {
variant: "pi",
textColor: "neutral600",
children: scheduledAt ? /*#__PURE__*/ jsx(RelativeTime, {
timestamp: new Date(scheduledAt)
}) : formatMessage({
id: 'content-releases.pages.Releases.not-scheduled',
defaultMessage: 'Not scheduled'
})
})
]
}),
/*#__PURE__*/ jsx(Badge, {
...getBadgeProps(status),
children: status
})
]
})
})
}, id))
});
};
/* -------------------------------------------------------------------------------------------------
* ReleasesPage
* -----------------------------------------------------------------------------------------------*/ const StyledAlert = styled(Alert)`
button {
display: none;
}
p + div {
margin-left: auto;
}
`;
const INITIAL_FORM_VALUES = {
name: '',
date: format(new Date(), 'yyyy-MM-dd'),
time: '',
isScheduled: true,
scheduledAt: null,
timezone: null
};
const ReleasesPage = ()=>{
const location = useLocation();
const [releaseModalShown, setReleaseModalShown] = React.useState(false);
const { toggleNotification } = useNotification();
const { formatMessage } = useIntl();
const navigate = useNavigate();
const { formatAPIError } = useAPIErrorHandler();
const [{ query }, setQuery] = useQueryParams();
const response = useGetReleasesQuery(query);
const { data, isLoading: isLoadingSettings } = useGetReleaseSettingsQuery();
const [createRelease, { isLoading: isSubmittingForm }] = useCreateReleaseMutation();
const { getFeature } = useLicenseLimits();
const { maximumReleases = 3 } = getFeature('cms-content-releases');
const { trackUsage } = useTracking();
const { allowedActions: { canCreate } } = useRBAC(PERMISSIONS);
const { isLoading: isLoadingReleases, isSuccess, isError } = response;
const activeTab = response?.currentData?.meta?.activeTab || 'pending';
// Check if we have some errors and show a notification to the user to explain the error
React.useEffect(()=>{
if (location?.state?.errors) {
toggleNotification({
type: 'danger',
title: formatMessage({
id: 'content-releases.pages.Releases.notification.error.title',
defaultMessage: 'Your request could not be processed.'
}),
message: formatMessage({
id: 'content-releases.pages.Releases.notification.error.message',
defaultMessage: 'Please try again or open another release.'
})
});
navigate('', {
replace: true,
state: null
});
}
}, [
formatMessage,
location?.state?.errors,
navigate,
toggleNotification
]);
const toggleAddReleaseModal = ()=>{
setReleaseModalShown((prev)=>!prev);
};
if (isLoadingReleases || isLoadingSettings) {
return /*#__PURE__*/ jsx(Page.Loading, {});
}
const totalPendingReleases = isSuccess && response.currentData?.meta?.pendingReleasesCount || 0;
const hasReachedMaximumPendingReleases = totalPendingReleases >= maximumReleases;
const handleTabChange = (tabValue)=>{
setQuery({
...query,
page: 1,
pageSize: response?.currentData?.meta?.pagination?.pageSize || 16,
filters: {
releasedAt: {
$notNull: tabValue !== 'pending'
}
}
});
};
const handleAddRelease = async ({ name, scheduledAt, timezone })=>{
const response = await createRelease({
name,
scheduledAt,
timezone
});
if ('data' in response) {
// When the response returns an object with 'data', handle success
toggleNotification({
type: 'success',
message: formatMessage({
id: 'content-releases.modal.release-created-notification-success',
defaultMessage: 'Release created.'
})
});
trackUsage('didCreateRelease');
navigate(response.data.data.id.toString());
} else if (isFetchError(response.error)) {
// When the response returns an object with 'error', handle fetch error
toggleNotification({
type: 'danger',
message: formatAPIError(response.error)
});
} else {
// Otherwise, the response returns an object with 'error', handle a generic error
toggleNotification({
type: 'danger',
message: formatMessage({
id: 'notification.error',
defaultMessage: 'An error occurred'
})
});
}
};
return /*#__PURE__*/ jsxs(Main, {
"aria-busy": isLoadingReleases || isLoadingSettings,
children: [
/*#__PURE__*/ jsx(Layouts.Header, {
title: formatMessage({
id: 'content-releases.pages.Releases.title',
defaultMessage: 'Releases'
}),
subtitle: formatMessage({
id: 'content-releases.pages.Releases.header-subtitle',
defaultMessage: 'Create and manage content updates'
}),
primaryAction: canCreate ? /*#__PURE__*/ jsx(Button, {
startIcon: /*#__PURE__*/ jsx(Plus, {}),
onClick: toggleAddReleaseModal,
disabled: hasReachedMaximumPendingReleases,
children: formatMessage({
id: 'content-releases.header.actions.add-release',
defaultMessage: 'New release'
})
}) : null
}),
/*#__PURE__*/ jsx(Layouts.Content, {
children: /*#__PURE__*/ jsxs(Fragment, {
children: [
hasReachedMaximumPendingReleases && /*#__PURE__*/ jsx(StyledAlert, {
marginBottom: 6,
action: /*#__PURE__*/ jsx(Link, {
href: "https://strapi.io/pricing-cloud",
isExternal: true,
children: formatMessage({
id: 'content-releases.pages.Releases.max-limit-reached.action',
defaultMessage: 'Explore plans'
})
}),
title: formatMessage({
id: 'content-releases.pages.Releases.max-limit-reached.title',
defaultMessage: 'You have reached the {number} pending {number, plural, one {release} other {releases}} limit.'
}, {
number: maximumReleases
}),
onClose: ()=>{},
closeLabel: "",
children: formatMessage({
id: 'content-releases.pages.Releases.max-limit-reached.message',
defaultMessage: 'Upgrade to manage an unlimited number of releases.'
})
}),
/*#__PURE__*/ jsxs(Tabs.Root, {
variant: "simple",
onValueChange: handleTabChange,
value: activeTab,
children: [
/*#__PURE__*/ jsxs(Box, {
paddingBottom: 8,
children: [
/*#__PURE__*/ jsxs(Tabs.List, {
"aria-label": formatMessage({
id: 'content-releases.pages.Releases.tab-group.label',
defaultMessage: 'Releases list'
}),
children: [
/*#__PURE__*/ jsx(Tabs.Trigger, {
value: "pending",
children: formatMessage({
id: 'content-releases.pages.Releases.tab.pending',
defaultMessage: 'Pending ({count})'
}, {
count: totalPendingReleases
})
}),
/*#__PURE__*/ jsx(Tabs.Trigger, {
value: "done",
children: formatMessage({
id: 'content-releases.pages.Releases.tab.done',
defaultMessage: 'Done'
})
})
]
}),
/*#__PURE__*/ jsx(Divider, {})
]
}),
/*#__PURE__*/ jsx(Tabs.Content, {
value: "pending",
children: /*#__PURE__*/ jsx(ReleasesGrid, {
sectionTitle: "pending",
releases: response?.currentData?.data,
isError: isError
})
}),
/*#__PURE__*/ jsx(Tabs.Content, {
value: "done",
children: /*#__PURE__*/ jsx(ReleasesGrid, {
sectionTitle: "done",
releases: response?.currentData?.data,
isError: isError
})
})
]
}),
/*#__PURE__*/ jsxs(Pagination.Root, {
...response?.currentData?.meta?.pagination,
defaultPageSize: response?.currentData?.meta?.pagination?.pageSize,
children: [
/*#__PURE__*/ jsx(Pagination.PageSize, {
options: [
'8',
'16',
'32',
'64'
]
}),
/*#__PURE__*/ jsx(Pagination.Links, {})
]
})
]
})
}),
/*#__PURE__*/ jsx(ReleaseModal, {
open: releaseModalShown,
handleClose: toggleAddReleaseModal,
handleSubmit: handleAddRelease,
isLoading: isSubmittingForm,
initialValues: {
...INITIAL_FORM_VALUES,
timezone: data?.data.defaultTimezone ? data.data.defaultTimezone.split('&')[1] : null
}
})
]
});
};
export { ReleasesPage, getBadgeProps };
//# sourceMappingURL=ReleasesPage.mjs.map
@@ -0,0 +1 @@
{"version":3,"file":"ReleasesPage.mjs","sources":["../../../admin/src/pages/ReleasesPage.tsx"],"sourcesContent":["import * as React from 'react';\n\nimport {\n Page,\n Pagination,\n useTracking,\n useAPIErrorHandler,\n useNotification,\n useQueryParams,\n useRBAC,\n isFetchError,\n Layouts,\n} from '@strapi/admin/strapi-admin';\nimport { useLicenseLimits } from '@strapi/admin/strapi-admin/ee';\nimport {\n Alert,\n Badge,\n Box,\n Button,\n Divider,\n EmptyStateLayout,\n Flex,\n Grid,\n Main,\n Tabs,\n Typography,\n Link,\n} from '@strapi/design-system';\nimport { Plus } from '@strapi/icons';\nimport { EmptyDocuments } from '@strapi/icons/symbols';\nimport { format } from 'date-fns';\nimport { useIntl } from 'react-intl';\nimport { useNavigate, useLocation, NavLink } from 'react-router-dom';\nimport { styled } from 'styled-components';\n\nimport { GetReleases, type Release } from '../../../shared/contracts/releases';\nimport { RelativeTime as BaseRelativeTime } from '../components/RelativeTime';\nimport { ReleaseModal, FormValues } from '../components/ReleaseModal';\nimport { PERMISSIONS } from '../constants';\nimport {\n useGetReleasesQuery,\n useGetReleaseSettingsQuery,\n GetReleasesQueryParams,\n useCreateReleaseMutation,\n} from '../services/release';\n\n/* -------------------------------------------------------------------------------------------------\n * ReleasesGrid\n * -----------------------------------------------------------------------------------------------*/\ninterface ReleasesGridProps {\n sectionTitle: 'pending' | 'done';\n releases?: GetReleases.Response['data'];\n isError?: boolean;\n}\n\nconst LinkCard = styled(Link)`\n display: block;\n`;\n\nconst RelativeTime = styled(BaseRelativeTime)`\n display: inline-block;\n &::first-letter {\n text-transform: uppercase;\n }\n`;\n\nconst getBadgeProps = (status: Release['status']) => {\n let color;\n switch (status) {\n case 'ready':\n color = 'success';\n break;\n case 'blocked':\n color = 'warning';\n break;\n case 'failed':\n color = 'danger';\n break;\n case 'done':\n color = 'primary';\n break;\n case 'empty':\n default:\n color = 'neutral';\n }\n\n return {\n textColor: `${color}600`,\n backgroundColor: `${color}100`,\n borderColor: `${color}200`,\n };\n};\n\nconst ReleasesGrid = ({ sectionTitle, releases = [], isError = false }: ReleasesGridProps) => {\n const { formatMessage } = useIntl();\n\n if (isError) {\n return <Page.Error />;\n }\n\n if (releases?.length === 0) {\n return (\n <EmptyStateLayout\n content={formatMessage(\n {\n id: 'content-releases.page.Releases.tab.emptyEntries',\n defaultMessage: 'No releases',\n },\n {\n target: sectionTitle,\n }\n )}\n icon={<EmptyDocuments width=\"16rem\" />}\n />\n );\n }\n\n return (\n <Grid.Root gap={4}>\n {releases.map(({ id, name, scheduledAt, status }) => (\n <Grid.Item col={3} s={6} xs={12} key={id} direction=\"column\" alignItems=\"stretch\">\n <LinkCard tag={NavLink} to={`${id}`} isExternal={false}>\n <Flex\n direction=\"column\"\n justifyContent=\"space-between\"\n padding={4}\n hasRadius\n background=\"neutral0\"\n shadow=\"tableShadow\"\n height=\"100%\"\n width=\"100%\"\n alignItems=\"start\"\n gap={4}\n >\n <Flex direction=\"column\" alignItems=\"start\" gap={1}>\n <Typography textColor=\"neutral800\" tag=\"h3\" variant=\"delta\" fontWeight=\"bold\">\n {name}\n </Typography>\n <Typography variant=\"pi\" textColor=\"neutral600\">\n {scheduledAt ? (\n <RelativeTime timestamp={new Date(scheduledAt)} />\n ) : (\n formatMessage({\n id: 'content-releases.pages.Releases.not-scheduled',\n defaultMessage: 'Not scheduled',\n })\n )}\n </Typography>\n </Flex>\n <Badge {...getBadgeProps(status)}>{status}</Badge>\n </Flex>\n </LinkCard>\n </Grid.Item>\n ))}\n </Grid.Root>\n );\n};\n\n/* -------------------------------------------------------------------------------------------------\n * ReleasesPage\n * -----------------------------------------------------------------------------------------------*/\n\nconst StyledAlert = styled(Alert)`\n button {\n display: none;\n }\n p + div {\n margin-left: auto;\n }\n`;\n\nconst INITIAL_FORM_VALUES = {\n name: '',\n date: format(new Date(), 'yyyy-MM-dd'),\n time: '',\n isScheduled: true,\n scheduledAt: null,\n timezone: null,\n} satisfies FormValuesLine truncated
@@ -0,0 +1,199 @@
'use strict';
var jsxRuntime = require('react/jsx-runtime');
var strapiAdmin = require('@strapi/admin/strapi-admin');
var designSystem = require('@strapi/design-system');
var icons = require('@strapi/icons');
var reactIntl = require('react-intl');
var hooks = require('../modules/hooks.js');
var release = require('../services/release.js');
var time = require('../utils/time.js');
var schemas = require('../validation/schemas.js');
const ReleasesSettingsPage = ()=>{
const { formatMessage } = reactIntl.useIntl();
const { formatAPIError } = strapiAdmin.useAPIErrorHandler();
const { toggleNotification } = strapiAdmin.useNotification();
const { data, isLoading: isLoadingSettings } = release.useGetReleaseSettingsQuery();
const [updateReleaseSettings, { isLoading: isSubmittingForm }] = release.useUpdateReleaseSettingsMutation();
const permissions = hooks.useTypedSelector((state)=>state.admin_app.permissions['settings']?.['releases']);
const { allowedActions: { canUpdate } } = strapiAdmin.useRBAC(permissions);
const { timezoneList } = time.getTimezones(new Date());
const handleSubmit = async (body)=>{
const { defaultTimezone } = body;
const isBodyTimezoneValid = timezoneList.some((timezone)=>timezone.value === defaultTimezone);
const newBody = !defaultTimezone || !isBodyTimezoneValid ? {
defaultTimezone: null
} : {
...body
};
try {
const response = await updateReleaseSettings(newBody);
if ('data' in response) {
toggleNotification({
type: 'success',
message: formatMessage({
id: 'content-releases.pages.Settings.releases.setting.default-timezone-notification-success',
defaultMessage: 'Default timezone updated.'
})
});
} else if (strapiAdmin.isFetchError(response.error)) {
toggleNotification({
type: 'danger',
message: formatAPIError(response.error)
});
} else {
toggleNotification({
type: 'danger',
message: formatMessage({
id: 'notification.error',
defaultMessage: 'An error occurred'
})
});
}
} catch (error) {
toggleNotification({
type: 'danger',
message: formatMessage({
id: 'notification.error',
defaultMessage: 'An error occurred'
})
});
}
};
if (isLoadingSettings) {
return /*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.Page.Loading, {});
}
return /*#__PURE__*/ jsxRuntime.jsxs(strapiAdmin.Layouts.Root, {
children: [
/*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.Page.Title, {
children: formatMessage({
id: 'Settings.PageTitle',
defaultMessage: 'Settings - {name}'
}, {
name: 'Releases'
})
}),
/*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.Page.Main, {
"aria-busy": isLoadingSettings,
tabIndex: -1,
children: /*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.Form, {
method: "PUT",
initialValues: {
defaultTimezone: data?.data.defaultTimezone
},
onSubmit: handleSubmit,
validationSchema: schemas.SETTINGS_SCHEMA,
children: ({ modified, isSubmitting })=>{
return /*#__PURE__*/ jsxRuntime.jsxs(jsxRuntime.Fragment, {
children: [
/*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.Layouts.Header, {
primaryAction: canUpdate ? /*#__PURE__*/ jsxRuntime.jsx(designSystem.Button, {
disabled: !modified || isSubmittingForm,
loading: isSubmitting,
startIcon: /*#__PURE__*/ jsxRuntime.jsx(icons.Check, {}),
type: "submit",
children: formatMessage({
id: 'global.save',
defaultMessage: 'Save'
})
}) : null,
title: formatMessage({
id: 'content-releases.pages.Settings.releases.title',
defaultMessage: 'Releases'
}),
subtitle: formatMessage({
id: 'content-releases.pages.Settings.releases.description',
defaultMessage: 'Create and manage content updates'
})
}),
/*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.Layouts.Content, {
children: /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
direction: "column",
background: "neutral0",
alignItems: "stretch",
padding: 6,
gap: 6,
shadow: "filterShadow",
hasRadius: true,
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
variant: "delta",
tag: "h2",
children: formatMessage({
id: 'content-releases.pages.Settings.releases.preferences.title',
defaultMessage: 'Preferences'
})
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Grid.Root, {
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Grid.Item, {
col: 6,
s: 12,
direction: "column",
alignItems: "stretch",
children: /*#__PURE__*/ jsxRuntime.jsx(TimezoneDropdown, {})
})
})
]
})
})
]
});
}
})
})
]
});
};
const TimezoneDropdown = ()=>{
const permissions = hooks.useTypedSelector((state)=>state.admin_app.permissions['settings']?.['releases']);
const { allowedActions: { canUpdate } } = strapiAdmin.useRBAC(permissions);
const { formatMessage } = reactIntl.useIntl();
const { timezoneList } = time.getTimezones(new Date());
const field = strapiAdmin.useField('defaultTimezone');
return /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Field.Root, {
name: "defaultTimezone",
hint: formatMessage({
id: 'content-releases.pages.Settings.releases.timezone.hint',
defaultMessage: 'The timezone of every release can still be changed individually. '
}),
error: field.error,
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Field.Label, {
children: formatMessage({
id: 'content-releases.pages.Settings.releases.timezone.label',
defaultMessage: 'Default timezone'
})
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Combobox, {
autocomplete: {
type: 'list',
filter: 'contains'
},
onChange: (value)=>field.onChange('defaultTimezone', value),
onTextValueChange: (value)=>field.onChange('defaultTimezone', value),
onClear: ()=>field.onChange('defaultTimezone', ''),
value: field.value,
disabled: !canUpdate,
children: timezoneList.map((timezone)=>/*#__PURE__*/ jsxRuntime.jsx(designSystem.ComboboxOption, {
value: timezone.value,
children: timezone.value.replace(/&/, ' ')
}, timezone.value))
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Field.Hint, {}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Field.Error, {})
]
});
};
/* -------------------------------------------------------------------------------------------------
* ProtectedSettingsPage
* -----------------------------------------------------------------------------------------------*/ const ProtectedReleasesSettingsPage = ()=>{
const permissions = hooks.useTypedSelector((state)=>state.admin_app.permissions['settings']?.['releases']?.read);
return /*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.Page.Protect, {
permissions: permissions,
children: /*#__PURE__*/ jsxRuntime.jsx(ReleasesSettingsPage, {})
});
};
exports.ProtectedReleasesSettingsPage = ProtectedReleasesSettingsPage;
//# sourceMappingURL=ReleasesSettingsPage.js.map
@@ -0,0 +1 @@
{"version":3,"file":"ReleasesSettingsPage.js","sources":["../../../admin/src/pages/ReleasesSettingsPage.tsx"],"sourcesContent":["import {\n Form,\n Layouts,\n Page,\n useAPIErrorHandler,\n isFetchError,\n useNotification,\n useField,\n useRBAC,\n} from '@strapi/admin/strapi-admin';\nimport {\n Button,\n Combobox,\n ComboboxOption,\n Field,\n Flex,\n Grid,\n Typography,\n} from '@strapi/design-system';\nimport { Check } from '@strapi/icons';\nimport { useIntl } from 'react-intl';\n\nimport { useTypedSelector } from '../modules/hooks';\nimport { useGetReleaseSettingsQuery, useUpdateReleaseSettingsMutation } from '../services/release';\nimport { getTimezones } from '../utils/time';\nimport { SETTINGS_SCHEMA } from '../validation/schemas';\n\nimport type { UpdateSettings } from '../../../shared/contracts/settings';\n\nconst ReleasesSettingsPage = () => {\n const { formatMessage } = useIntl();\n const { formatAPIError } = useAPIErrorHandler();\n const { toggleNotification } = useNotification();\n const { data, isLoading: isLoadingSettings } = useGetReleaseSettingsQuery();\n const [updateReleaseSettings, { isLoading: isSubmittingForm }] =\n useUpdateReleaseSettingsMutation();\n const permissions = useTypedSelector(\n (state) => state.admin_app.permissions['settings']?.['releases']\n );\n const {\n allowedActions: { canUpdate },\n } = useRBAC(permissions);\n\n const { timezoneList } = getTimezones(new Date());\n\n const handleSubmit = async (body: UpdateSettings.Request['body']) => {\n const { defaultTimezone } = body;\n const isBodyTimezoneValid = timezoneList.some((timezone) => timezone.value === defaultTimezone);\n const newBody =\n !defaultTimezone || !isBodyTimezoneValid ? { defaultTimezone: null } : { ...body };\n try {\n const response = await updateReleaseSettings(newBody);\n\n if ('data' in response) {\n toggleNotification({\n type: 'success',\n message: formatMessage({\n id: 'content-releases.pages.Settings.releases.setting.default-timezone-notification-success',\n defaultMessage: 'Default timezone updated.',\n }),\n });\n } else if (isFetchError(response.error)) {\n toggleNotification({\n type: 'danger',\n message: formatAPIError(response.error),\n });\n } else {\n toggleNotification({\n type: 'danger',\n message: formatMessage({ id: 'notification.error', defaultMessage: 'An error occurred' }),\n });\n }\n } catch (error) {\n toggleNotification({\n type: 'danger',\n message: formatMessage({ id: 'notification.error', defaultMessage: 'An error occurred' }),\n });\n }\n };\n\n if (isLoadingSettings) {\n return <Page.Loading />;\n }\n\n return (\n <Layouts.Root>\n <Page.Title>\n {formatMessage(\n { id: 'Settings.PageTitle', defaultMessage: 'Settings - {name}' },\n {\n name: 'Releases',\n }\n )}\n </Page.Title>\n <Page.Main aria-busy={isLoadingSettings} tabIndex={-1}>\n <Form\n method=\"PUT\"\n initialValues={{\n defaultTimezone: data?.data.defaultTimezone,\n }}\n onSubmit={handleSubmit}\n validationSchema={SETTINGS_SCHEMA}\n >\n {({ modified, isSubmitting }: { modified: boolean; isSubmitting: boolean }) => {\n return (\n <>\n <Layouts.Header\n primaryAction={\n canUpdate ? (\n <Button\n disabled={!modified || isSubmittingForm}\n loading={isSubmitting}\n startIcon={<Check />}\n type=\"submit\"\n >\n {formatMessage({\n id: 'global.save',\n defaultMessage: 'Save',\n })}\n </Button>\n ) : null\n }\n title={formatMessage({\n id: 'content-releases.pages.Settings.releases.title',\n defaultMessage: 'Releases',\n })}\n subtitle={formatMessage({\n id: 'content-releases.pages.Settings.releases.description',\n defaultMessage: 'Create and manage content updates',\n })}\n />\n <Layouts.Content>\n <Flex\n direction=\"column\"\n background=\"neutral0\"\n alignItems=\"stretch\"\n padding={6}\n gap={6}\n shadow=\"filterShadow\"\n hasRadius\n >\n <Typography variant=\"delta\" tag=\"h2\">\n {formatMessage({\n Line truncated
@@ -0,0 +1,197 @@
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
import { Page, useAPIErrorHandler, useNotification, useRBAC, Layouts, Form, useField, isFetchError } from '@strapi/admin/strapi-admin';
import { Button, Flex, Typography, Grid, Field, Combobox, ComboboxOption } from '@strapi/design-system';
import { Check } from '@strapi/icons';
import { useIntl } from 'react-intl';
import { useTypedSelector } from '../modules/hooks.mjs';
import { useGetReleaseSettingsQuery, useUpdateReleaseSettingsMutation } from '../services/release.mjs';
import { getTimezones } from '../utils/time.mjs';
import { SETTINGS_SCHEMA } from '../validation/schemas.mjs';
const ReleasesSettingsPage = ()=>{
const { formatMessage } = useIntl();
const { formatAPIError } = useAPIErrorHandler();
const { toggleNotification } = useNotification();
const { data, isLoading: isLoadingSettings } = useGetReleaseSettingsQuery();
const [updateReleaseSettings, { isLoading: isSubmittingForm }] = useUpdateReleaseSettingsMutation();
const permissions = useTypedSelector((state)=>state.admin_app.permissions['settings']?.['releases']);
const { allowedActions: { canUpdate } } = useRBAC(permissions);
const { timezoneList } = getTimezones(new Date());
const handleSubmit = async (body)=>{
const { defaultTimezone } = body;
const isBodyTimezoneValid = timezoneList.some((timezone)=>timezone.value === defaultTimezone);
const newBody = !defaultTimezone || !isBodyTimezoneValid ? {
defaultTimezone: null
} : {
...body
};
try {
const response = await updateReleaseSettings(newBody);
if ('data' in response) {
toggleNotification({
type: 'success',
message: formatMessage({
id: 'content-releases.pages.Settings.releases.setting.default-timezone-notification-success',
defaultMessage: 'Default timezone updated.'
})
});
} else if (isFetchError(response.error)) {
toggleNotification({
type: 'danger',
message: formatAPIError(response.error)
});
} else {
toggleNotification({
type: 'danger',
message: formatMessage({
id: 'notification.error',
defaultMessage: 'An error occurred'
})
});
}
} catch (error) {
toggleNotification({
type: 'danger',
message: formatMessage({
id: 'notification.error',
defaultMessage: 'An error occurred'
})
});
}
};
if (isLoadingSettings) {
return /*#__PURE__*/ jsx(Page.Loading, {});
}
return /*#__PURE__*/ jsxs(Layouts.Root, {
children: [
/*#__PURE__*/ jsx(Page.Title, {
children: formatMessage({
id: 'Settings.PageTitle',
defaultMessage: 'Settings - {name}'
}, {
name: 'Releases'
})
}),
/*#__PURE__*/ jsx(Page.Main, {
"aria-busy": isLoadingSettings,
tabIndex: -1,
children: /*#__PURE__*/ jsx(Form, {
method: "PUT",
initialValues: {
defaultTimezone: data?.data.defaultTimezone
},
onSubmit: handleSubmit,
validationSchema: SETTINGS_SCHEMA,
children: ({ modified, isSubmitting })=>{
return /*#__PURE__*/ jsxs(Fragment, {
children: [
/*#__PURE__*/ jsx(Layouts.Header, {
primaryAction: canUpdate ? /*#__PURE__*/ jsx(Button, {
disabled: !modified || isSubmittingForm,
loading: isSubmitting,
startIcon: /*#__PURE__*/ jsx(Check, {}),
type: "submit",
children: formatMessage({
id: 'global.save',
defaultMessage: 'Save'
})
}) : null,
title: formatMessage({
id: 'content-releases.pages.Settings.releases.title',
defaultMessage: 'Releases'
}),
subtitle: formatMessage({
id: 'content-releases.pages.Settings.releases.description',
defaultMessage: 'Create and manage content updates'
})
}),
/*#__PURE__*/ jsx(Layouts.Content, {
children: /*#__PURE__*/ jsxs(Flex, {
direction: "column",
background: "neutral0",
alignItems: "stretch",
padding: 6,
gap: 6,
shadow: "filterShadow",
hasRadius: true,
children: [
/*#__PURE__*/ jsx(Typography, {
variant: "delta",
tag: "h2",
children: formatMessage({
id: 'content-releases.pages.Settings.releases.preferences.title',
defaultMessage: 'Preferences'
})
}),
/*#__PURE__*/ jsx(Grid.Root, {
children: /*#__PURE__*/ jsx(Grid.Item, {
col: 6,
s: 12,
direction: "column",
alignItems: "stretch",
children: /*#__PURE__*/ jsx(TimezoneDropdown, {})
})
})
]
})
})
]
});
}
})
})
]
});
};
const TimezoneDropdown = ()=>{
const permissions = useTypedSelector((state)=>state.admin_app.permissions['settings']?.['releases']);
const { allowedActions: { canUpdate } } = useRBAC(permissions);
const { formatMessage } = useIntl();
const { timezoneList } = getTimezones(new Date());
const field = useField('defaultTimezone');
return /*#__PURE__*/ jsxs(Field.Root, {
name: "defaultTimezone",
hint: formatMessage({
id: 'content-releases.pages.Settings.releases.timezone.hint',
defaultMessage: 'The timezone of every release can still be changed individually. '
}),
error: field.error,
children: [
/*#__PURE__*/ jsx(Field.Label, {
children: formatMessage({
id: 'content-releases.pages.Settings.releases.timezone.label',
defaultMessage: 'Default timezone'
})
}),
/*#__PURE__*/ jsx(Combobox, {
autocomplete: {
type: 'list',
filter: 'contains'
},
onChange: (value)=>field.onChange('defaultTimezone', value),
onTextValueChange: (value)=>field.onChange('defaultTimezone', value),
onClear: ()=>field.onChange('defaultTimezone', ''),
value: field.value,
disabled: !canUpdate,
children: timezoneList.map((timezone)=>/*#__PURE__*/ jsx(ComboboxOption, {
value: timezone.value,
children: timezone.value.replace(/&/, ' ')
}, timezone.value))
}),
/*#__PURE__*/ jsx(Field.Hint, {}),
/*#__PURE__*/ jsx(Field.Error, {})
]
});
};
/* -------------------------------------------------------------------------------------------------
* ProtectedSettingsPage
* -----------------------------------------------------------------------------------------------*/ const ProtectedReleasesSettingsPage = ()=>{
const permissions = useTypedSelector((state)=>state.admin_app.permissions['settings']?.['releases']?.read);
return /*#__PURE__*/ jsx(Page.Protect, {
permissions: permissions,
children: /*#__PURE__*/ jsx(ReleasesSettingsPage, {})
});
};
export { ProtectedReleasesSettingsPage };
//# sourceMappingURL=ReleasesSettingsPage.mjs.map
@@ -0,0 +1 @@
{"version":3,"file":"ReleasesSettingsPage.mjs","sources":["../../../admin/src/pages/ReleasesSettingsPage.tsx"],"sourcesContent":["import {\n Form,\n Layouts,\n Page,\n useAPIErrorHandler,\n isFetchError,\n useNotification,\n useField,\n useRBAC,\n} from '@strapi/admin/strapi-admin';\nimport {\n Button,\n Combobox,\n ComboboxOption,\n Field,\n Flex,\n Grid,\n Typography,\n} from '@strapi/design-system';\nimport { Check } from '@strapi/icons';\nimport { useIntl } from 'react-intl';\n\nimport { useTypedSelector } from '../modules/hooks';\nimport { useGetReleaseSettingsQuery, useUpdateReleaseSettingsMutation } from '../services/release';\nimport { getTimezones } from '../utils/time';\nimport { SETTINGS_SCHEMA } from '../validation/schemas';\n\nimport type { UpdateSettings } from '../../../shared/contracts/settings';\n\nconst ReleasesSettingsPage = () => {\n const { formatMessage } = useIntl();\n const { formatAPIError } = useAPIErrorHandler();\n const { toggleNotification } = useNotification();\n const { data, isLoading: isLoadingSettings } = useGetReleaseSettingsQuery();\n const [updateReleaseSettings, { isLoading: isSubmittingForm }] =\n useUpdateReleaseSettingsMutation();\n const permissions = useTypedSelector(\n (state) => state.admin_app.permissions['settings']?.['releases']\n );\n const {\n allowedActions: { canUpdate },\n } = useRBAC(permissions);\n\n const { timezoneList } = getTimezones(new Date());\n\n const handleSubmit = async (body: UpdateSettings.Request['body']) => {\n const { defaultTimezone } = body;\n const isBodyTimezoneValid = timezoneList.some((timezone) => timezone.value === defaultTimezone);\n const newBody =\n !defaultTimezone || !isBodyTimezoneValid ? { defaultTimezone: null } : { ...body };\n try {\n const response = await updateReleaseSettings(newBody);\n\n if ('data' in response) {\n toggleNotification({\n type: 'success',\n message: formatMessage({\n id: 'content-releases.pages.Settings.releases.setting.default-timezone-notification-success',\n defaultMessage: 'Default timezone updated.',\n }),\n });\n } else if (isFetchError(response.error)) {\n toggleNotification({\n type: 'danger',\n message: formatAPIError(response.error),\n });\n } else {\n toggleNotification({\n type: 'danger',\n message: formatMessage({ id: 'notification.error', defaultMessage: 'An error occurred' }),\n });\n }\n } catch (error) {\n toggleNotification({\n type: 'danger',\n message: formatMessage({ id: 'notification.error', defaultMessage: 'An error occurred' }),\n });\n }\n };\n\n if (isLoadingSettings) {\n return <Page.Loading />;\n }\n\n return (\n <Layouts.Root>\n <Page.Title>\n {formatMessage(\n { id: 'Settings.PageTitle', defaultMessage: 'Settings - {name}' },\n {\n name: 'Releases',\n }\n )}\n </Page.Title>\n <Page.Main aria-busy={isLoadingSettings} tabIndex={-1}>\n <Form\n method=\"PUT\"\n initialValues={{\n defaultTimezone: data?.data.defaultTimezone,\n }}\n onSubmit={handleSubmit}\n validationSchema={SETTINGS_SCHEMA}\n >\n {({ modified, isSubmitting }: { modified: boolean; isSubmitting: boolean }) => {\n return (\n <>\n <Layouts.Header\n primaryAction={\n canUpdate ? (\n <Button\n disabled={!modified || isSubmittingForm}\n loading={isSubmitting}\n startIcon={<Check />}\n type=\"submit\"\n >\n {formatMessage({\n id: 'global.save',\n defaultMessage: 'Save',\n })}\n </Button>\n ) : null\n }\n title={formatMessage({\n id: 'content-releases.pages.Settings.releases.title',\n defaultMessage: 'Releases',\n })}\n subtitle={formatMessage({\n id: 'content-releases.pages.Settings.releases.description',\n defaultMessage: 'Create and manage content updates',\n })}\n />\n <Layouts.Content>\n <Flex\n direction=\"column\"\n background=\"neutral0\"\n alignItems=\"stretch\"\n padding={6}\n gap={6}\n shadow=\"filterShadow\"\n hasRadius\n >\n <Typography variant=\"delta\" tag=\"h2\">\n {formatMessage({\n Line truncated
+6
View File
@@ -0,0 +1,6 @@
'use strict';
const pluginId = 'content-releases';
exports.pluginId = pluginId;
//# sourceMappingURL=pluginId.js.map
@@ -0,0 +1 @@
{"version":3,"file":"pluginId.js","sources":["../../admin/src/pluginId.ts"],"sourcesContent":["export const pluginId = 'content-releases';\n"],"names":["pluginId"],"mappings":";;AAAO,MAAMA,WAAW;;;;"}
+4
View File
@@ -0,0 +1,4 @@
const pluginId = 'content-releases';
export { pluginId };
//# sourceMappingURL=pluginId.mjs.map
@@ -0,0 +1 @@
{"version":3,"file":"pluginId.mjs","sources":["../../admin/src/pluginId.ts"],"sourcesContent":["export const pluginId = 'content-releases';\n"],"names":["pluginId"],"mappings":"AAAO,MAAMA,WAAW;;;;"}
@@ -0,0 +1,464 @@
'use strict';
var strapiAdmin = require('@strapi/admin/strapi-admin');
// TODO: move this into the admin code & expose an improved version of enhanceEndpoints or a new function
const extendInvalidatesTags = (endpoint, extraTags)=>{
if (!endpoint) {
return;
}
const originalInvalidatesTags = endpoint.invalidatesTags;
const newInvalidatesTags = (result, err, args, meta)=>{
const originalTags = typeof originalInvalidatesTags === 'function' ? originalInvalidatesTags(result, err, args, meta) : originalInvalidatesTags;
return [
...originalTags ?? [],
...extraTags
];
};
Object.assign(endpoint, {
invalidatesTags: newInvalidatesTags
});
};
const releaseApi = strapiAdmin.adminApi.enhanceEndpoints({
addTagTypes: [
'Release',
'ReleaseAction',
'EntriesInRelease',
'ReleaseSettings',
'Document'
],
endpoints: {
updateDocument (endpoint) {
extendInvalidatesTags(endpoint, [
{
type: 'Release',
id: 'LIST'
},
{
type: 'ReleaseAction',
id: 'LIST'
}
]);
},
deleteDocument (endpoint) {
extendInvalidatesTags(endpoint, [
{
type: 'Release',
id: 'LIST'
},
{
type: 'ReleaseAction',
id: 'LIST'
}
]);
},
deleteManyDocuments (endpoint) {
extendInvalidatesTags(endpoint, [
{
type: 'Release',
id: 'LIST'
},
{
type: 'ReleaseAction',
id: 'LIST'
}
]);
},
discardDocument (endpoint) {
extendInvalidatesTags(endpoint, [
{
type: 'Release',
id: 'LIST'
},
{
type: 'ReleaseAction',
id: 'LIST'
}
]);
},
createWorkflow (endpoint) {
extendInvalidatesTags(endpoint, [
{
type: 'Release',
id: 'LIST'
},
{
type: 'ReleaseAction',
id: 'LIST'
}
]);
},
updateWorkflow (endpoint) {
extendInvalidatesTags(endpoint, [
{
type: 'Release',
id: 'LIST'
},
{
type: 'ReleaseAction',
id: 'LIST'
}
]);
},
deleteWorkflow (endpoint) {
extendInvalidatesTags(endpoint, [
{
type: 'Release',
id: 'LIST'
},
{
type: 'ReleaseAction',
id: 'LIST'
}
]);
}
}
}).injectEndpoints({
endpoints: (build)=>{
return {
getReleasesForEntry: build.query({
query (params) {
return {
url: '/content-releases/getByDocumentAttached',
method: 'GET',
config: {
params
}
};
},
providesTags: (result)=>result ? [
...result.data.map(({ id })=>({
type: 'Release',
id
})),
{
type: 'Release',
id: 'LIST'
}
] : []
}),
getReleases: build.query({
query ({ page, pageSize, filters } = {
page: 1,
pageSize: 16,
filters: {
releasedAt: {
$notNull: false
}
}
}) {
return {
url: '/content-releases',
method: 'GET',
config: {
params: {
page: page || 1,
pageSize: pageSize || 16,
filters: filters || {
releasedAt: {
$notNull: false
}
}
}
}
};
},
transformResponse (response, meta, arg) {
const releasedAtValue = arg?.filters?.releasedAt?.$notNull;
const isActiveDoneTab = releasedAtValue === 'true';
const newResponse = {
...response,
meta: {
...response.meta,
activeTab: isActiveDoneTab ? 'done' : 'pending'
}
};
return newResponse;
},
providesTags: (result)=>result ? [
...result.data.map(({ id })=>({
type: 'Release',
id
})),
{
type: 'Release',
id: 'LIST'
}
] : [
{
type: 'Release',
id: 'LIST'
}
]
}),
getRelease: build.query({
query ({ id }) {
return {
url: `/content-releases/${id}`,
method: 'GET'
};
},
providesTags: (result, error, arg)=>[
{
type: 'Release',
id: 'LIST'
},
{
type: 'Release',
id: arg.id
}
]
}),
getReleaseActions: build.query({
query ({ releaseId, ...params }) {
return {
url: `/content-releases/${releaseId}/actions`,
method: 'GET',
config: {
params
}
};
},
providesTags: [
{
type: 'ReleaseAction',
id: 'LIST'
}
]
}),
createRelease: build.mutation({
query (data) {
return {
url: '/content-releases',
method: 'POST',
data
};
},
invalidatesTags: [
{
type: 'Release',
id: 'LIST'
}
]
}),
updateRelease: build.mutation({
query ({ id, ...data }) {
return {
url: `/content-releases/${id}`,
method: 'PUT',
data
};
},
invalidatesTags: (result, error, arg)=>[
{
type: 'Release',
id: arg.id
}
]
}),
createReleaseAction: build.mutation({
query ({ body, params }) {
return {
url: `/content-releases/${params.releaseId}/actions`,
method: 'POST',
data: body
};
},
invalidatesTags: [
{
type: 'Release',
id: 'LIST'
},
{
type: 'ReleaseAction',
id: 'LIST'
}
]
}),
createManyReleaseActions: build.mutation({
query ({ body, params }) {
return {
url: `/content-releases/${params.releaseId}/actions/bulk`,
method: 'POST',
data: body
};
},
invalidatesTags: [
{
type: 'Release',
id: 'LIST'
},
{
type: 'ReleaseAction',
id: 'LIST'
},
{
type: 'EntriesInRelease'
}
]
}),
updateReleaseAction: build.mutation({
query ({ body, params }) {
return {
url: `/content-releases/${params.releaseId}/actions/${params.actionId}`,
method: 'PUT',
data: body
};
},
invalidatesTags: (res, error, arg)=>[
{
type: 'ReleaseAction',
id: 'LIST'
},
{
type: 'Release',
id: 'LIST'
},
{
type: 'Release',
id: arg.params.releaseId
}
],
async onQueryStarted ({ body, params, query, actionPath }, { dispatch, queryFulfilled }) {
// We need to mimic the same params received by the getReleaseActions query
const paramsWithoutActionId = {
releaseId: params.releaseId,
...query
};
const patchResult = dispatch(releaseApi.util.updateQueryData('getReleaseActions', paramsWithoutActionId, (draft)=>{
const [key, index] = actionPath;
const action = draft.data[key][index];
if (action) {
action.type = body.type;
}
}));
try {
await queryFulfilled;
} catch {
patchResult.undo();
}
}
}),
deleteReleaseAction: build.mutation({
query ({ params }) {
return {
url: `/content-releases/${params.releaseId}/actions/${params.actionId}`,
method: 'DELETE'
};
},
invalidatesTags: (result, error, arg)=>[
{
type: 'Release',
id: 'LIST'
},
{
type: 'Release',
id: arg.params.releaseId
},
{
type: 'ReleaseAction',
id: 'LIST'
},
{
type: 'EntriesInRelease'
}
]
}),
publishRelease: build.mutation({
query ({ id }) {
return {
url: `/content-releases/${id}/publish`,
method: 'POST'
};
},
invalidatesTags: (result, error, arg)=>[
{
type: 'Release',
id: arg.id
},
{
type: 'Document',
id: `ALL_LIST`
}
]
}),
deleteRelease: build.mutation({
query ({ id }) {
return {
url: `/content-releases/${id}`,
method: 'DELETE'
};
},
invalidatesTags: ()=>[
{
type: 'Release',
id: 'LIST'
},
{
type: 'EntriesInRelease'
}
]
}),
getMappedEntriesInReleases: build.query({
query (params) {
return {
url: '/content-releases/mapEntriesToReleases',
method: 'GET',
config: {
params
}
};
},
transformResponse (response) {
return response.data;
},
providesTags: [
{
type: 'EntriesInRelease'
}
]
}),
getReleaseSettings: build.query({
query: ()=>'/content-releases/settings',
providesTags: [
{
type: 'ReleaseSettings'
}
]
}),
updateReleaseSettings: build.mutation({
query (data) {
return {
url: '/content-releases/settings',
method: 'PUT',
data
};
},
invalidatesTags: [
{
type: 'ReleaseSettings'
}
]
})
};
}
});
const { useGetReleasesQuery, useGetReleasesForEntryQuery, useGetReleaseQuery, useGetReleaseActionsQuery, useCreateReleaseMutation, useCreateReleaseActionMutation, useCreateManyReleaseActionsMutation, useUpdateReleaseMutation, useUpdateReleaseActionMutation, usePublishReleaseMutation, useDeleteReleaseActionMutation, useDeleteReleaseMutation, useGetMappedEntriesInReleasesQuery, useGetReleaseSettingsQuery, useUpdateReleaseSettingsMutation } = releaseApi;
exports.releaseApi = releaseApi;
exports.useCreateManyReleaseActionsMutation = useCreateManyReleaseActionsMutation;
exports.useCreateReleaseActionMutation = useCreateReleaseActionMutation;
exports.useCreateReleaseMutation = useCreateReleaseMutation;
exports.useDeleteReleaseActionMutation = useDeleteReleaseActionMutation;
exports.useDeleteReleaseMutation = useDeleteReleaseMutation;
exports.useGetMappedEntriesInReleasesQuery = useGetMappedEntriesInReleasesQuery;
exports.useGetReleaseActionsQuery = useGetReleaseActionsQuery;
exports.useGetReleaseQuery = useGetReleaseQuery;
exports.useGetReleaseSettingsQuery = useGetReleaseSettingsQuery;
exports.useGetReleasesForEntryQuery = useGetReleasesForEntryQuery;
exports.useGetReleasesQuery = useGetReleasesQuery;
exports.usePublishReleaseMutation = usePublishReleaseMutation;
exports.useUpdateReleaseActionMutation = useUpdateReleaseActionMutation;
exports.useUpdateReleaseMutation = useUpdateReleaseMutation;
exports.useUpdateReleaseSettingsMutation = useUpdateReleaseSettingsMutation;
//# sourceMappingURL=release.js.map
@@ -0,0 +1 @@
{"version":3,"file":"release.js","sources":["../../../admin/src/services/release.ts"],"sourcesContent":["import { adminApi } from '@strapi/admin/strapi-admin';\n\nimport {\n CreateReleaseAction,\n CreateManyReleaseActions,\n DeleteReleaseAction,\n} from '../../../shared/contracts/release-actions';\n\nimport type {\n GetReleaseActions,\n UpdateReleaseAction,\n ReleaseActionGroupBy,\n} from '../../../shared/contracts/release-actions';\nimport type {\n CreateRelease,\n DeleteRelease,\n GetReleases,\n GetReleasesByDocumentAttached,\n UpdateRelease,\n GetRelease,\n PublishRelease,\n MapEntriesToReleases,\n} from '../../../shared/contracts/releases';\nimport type { GetSettings, UpdateSettings } from '../../../shared/contracts/settings';\nimport type { EndpointDefinition } from '@reduxjs/toolkit/query';\n\nexport interface GetReleasesQueryParams {\n page?: number;\n pageSize?: number;\n filters?: {\n releasedAt?: {\n // TODO: this should be a boolean, find a way to avoid strings\n $notNull?: boolean | 'true' | 'false';\n };\n };\n}\n\nexport interface GetReleaseActionsQueryParams {\n page?: number;\n pageSize?: number;\n groupBy?: ReleaseActionGroupBy;\n}\n\ntype GetReleasesTabResponse = GetReleases.Response & {\n meta: {\n activeTab: 'pending' | 'done';\n };\n};\n\ntype AnyEndpointDefinition = EndpointDefinition<any, any, any, any>;\n\n// TODO: move this into the admin code & expose an improved version of enhanceEndpoints or a new function\nconst extendInvalidatesTags = (\n endpoint: AnyEndpointDefinition,\n extraTags: string[] | { type: string; id: string }[]\n) => {\n if (!endpoint) {\n return;\n }\n\n const originalInvalidatesTags = endpoint.invalidatesTags;\n\n const newInvalidatesTags: AnyEndpointDefinition['invalidatesTags'] = (\n result,\n err,\n args,\n meta\n ) => {\n const originalTags =\n typeof originalInvalidatesTags === 'function'\n ? originalInvalidatesTags(result, err, args, meta)\n : originalInvalidatesTags;\n\n return [...(originalTags ?? []), ...extraTags];\n };\n\n Object.assign(endpoint, { invalidatesTags: newInvalidatesTags });\n};\n\nconst releaseApi = adminApi\n .enhanceEndpoints({\n addTagTypes: ['Release', 'ReleaseAction', 'EntriesInRelease', 'ReleaseSettings', 'Document'],\n endpoints: {\n updateDocument(endpoint: AnyEndpointDefinition) {\n extendInvalidatesTags(endpoint, [\n { type: 'Release', id: 'LIST' },\n { type: 'ReleaseAction', id: 'LIST' },\n ]);\n },\n deleteDocument(endpoint: AnyEndpointDefinition) {\n extendInvalidatesTags(endpoint, [\n { type: 'Release', id: 'LIST' },\n { type: 'ReleaseAction', id: 'LIST' },\n ]);\n },\n deleteManyDocuments(endpoint: AnyEndpointDefinition) {\n extendInvalidatesTags(endpoint, [\n { type: 'Release', id: 'LIST' },\n { type: 'ReleaseAction', id: 'LIST' },\n ]);\n },\n discardDocument(endpoint: AnyEndpointDefinition) {\n extendInvalidatesTags(endpoint, [\n { type: 'Release', id: 'LIST' },\n { type: 'ReleaseAction', id: 'LIST' },\n ]);\n },\n createWorkflow(endpoint: AnyEndpointDefinition) {\n extendInvalidatesTags(endpoint, [\n { type: 'Release', id: 'LIST' },\n { type: 'ReleaseAction', id: 'LIST' },\n ]);\n },\n updateWorkflow(endpoint: AnyEndpointDefinition) {\n extendInvalidatesTags(endpoint, [\n { type: 'Release', id: 'LIST' },\n { type: 'ReleaseAction', id: 'LIST' },\n ]);\n },\n deleteWorkflow(endpoint: AnyEndpointDefinition) {\n extendInvalidatesTags(endpoint, [\n { type: 'Release', id: 'LIST' },\n { type: 'ReleaseAction', id: 'LIST' },\n ]);\n },\n },\n })\n .injectEndpoints({\n endpoints: (build) => {\n return {\n getReleasesForEntry: build.query<\n GetReleasesByDocumentAttached.Response,\n Partial<GetReleasesByDocumentAttached.Request['query']>\n >({\n query(params) {\n return {\n url: '/content-releases/getByDocumentAttached',\n method: 'GET',\n config: {\n params,\n },\n };\n },\n providesTags: (result) =>\n result\n ? [\n ...result.data.map(({ id }) => ({ type: 'Release' as const, id })),\n { type: 'Release', id: 'LIST' },\n ]\n : [],\n }),\n getReleases: build.query<GetReleasesTabResponse, GetReleasesQueryParams | void>({\n query(\n { page, pageSize, filters } = {\n page: 1,\n pageSize: 16,\n filters: {\n releasedAt: {\n $notNull: false,\n },\n },\n }\n )Line truncated
@@ -0,0 +1,447 @@
import { adminApi } from '@strapi/admin/strapi-admin';
// TODO: move this into the admin code & expose an improved version of enhanceEndpoints or a new function
const extendInvalidatesTags = (endpoint, extraTags)=>{
if (!endpoint) {
return;
}
const originalInvalidatesTags = endpoint.invalidatesTags;
const newInvalidatesTags = (result, err, args, meta)=>{
const originalTags = typeof originalInvalidatesTags === 'function' ? originalInvalidatesTags(result, err, args, meta) : originalInvalidatesTags;
return [
...originalTags ?? [],
...extraTags
];
};
Object.assign(endpoint, {
invalidatesTags: newInvalidatesTags
});
};
const releaseApi = adminApi.enhanceEndpoints({
addTagTypes: [
'Release',
'ReleaseAction',
'EntriesInRelease',
'ReleaseSettings',
'Document'
],
endpoints: {
updateDocument (endpoint) {
extendInvalidatesTags(endpoint, [
{
type: 'Release',
id: 'LIST'
},
{
type: 'ReleaseAction',
id: 'LIST'
}
]);
},
deleteDocument (endpoint) {
extendInvalidatesTags(endpoint, [
{
type: 'Release',
id: 'LIST'
},
{
type: 'ReleaseAction',
id: 'LIST'
}
]);
},
deleteManyDocuments (endpoint) {
extendInvalidatesTags(endpoint, [
{
type: 'Release',
id: 'LIST'
},
{
type: 'ReleaseAction',
id: 'LIST'
}
]);
},
discardDocument (endpoint) {
extendInvalidatesTags(endpoint, [
{
type: 'Release',
id: 'LIST'
},
{
type: 'ReleaseAction',
id: 'LIST'
}
]);
},
createWorkflow (endpoint) {
extendInvalidatesTags(endpoint, [
{
type: 'Release',
id: 'LIST'
},
{
type: 'ReleaseAction',
id: 'LIST'
}
]);
},
updateWorkflow (endpoint) {
extendInvalidatesTags(endpoint, [
{
type: 'Release',
id: 'LIST'
},
{
type: 'ReleaseAction',
id: 'LIST'
}
]);
},
deleteWorkflow (endpoint) {
extendInvalidatesTags(endpoint, [
{
type: 'Release',
id: 'LIST'
},
{
type: 'ReleaseAction',
id: 'LIST'
}
]);
}
}
}).injectEndpoints({
endpoints: (build)=>{
return {
getReleasesForEntry: build.query({
query (params) {
return {
url: '/content-releases/getByDocumentAttached',
method: 'GET',
config: {
params
}
};
},
providesTags: (result)=>result ? [
...result.data.map(({ id })=>({
type: 'Release',
id
})),
{
type: 'Release',
id: 'LIST'
}
] : []
}),
getReleases: build.query({
query ({ page, pageSize, filters } = {
page: 1,
pageSize: 16,
filters: {
releasedAt: {
$notNull: false
}
}
}) {
return {
url: '/content-releases',
method: 'GET',
config: {
params: {
page: page || 1,
pageSize: pageSize || 16,
filters: filters || {
releasedAt: {
$notNull: false
}
}
}
}
};
},
transformResponse (response, meta, arg) {
const releasedAtValue = arg?.filters?.releasedAt?.$notNull;
const isActiveDoneTab = releasedAtValue === 'true';
const newResponse = {
...response,
meta: {
...response.meta,
activeTab: isActiveDoneTab ? 'done' : 'pending'
}
};
return newResponse;
},
providesTags: (result)=>result ? [
...result.data.map(({ id })=>({
type: 'Release',
id
})),
{
type: 'Release',
id: 'LIST'
}
] : [
{
type: 'Release',
id: 'LIST'
}
]
}),
getRelease: build.query({
query ({ id }) {
return {
url: `/content-releases/${id}`,
method: 'GET'
};
},
providesTags: (result, error, arg)=>[
{
type: 'Release',
id: 'LIST'
},
{
type: 'Release',
id: arg.id
}
]
}),
getReleaseActions: build.query({
query ({ releaseId, ...params }) {
return {
url: `/content-releases/${releaseId}/actions`,
method: 'GET',
config: {
params
}
};
},
providesTags: [
{
type: 'ReleaseAction',
id: 'LIST'
}
]
}),
createRelease: build.mutation({
query (data) {
return {
url: '/content-releases',
method: 'POST',
data
};
},
invalidatesTags: [
{
type: 'Release',
id: 'LIST'
}
]
}),
updateRelease: build.mutation({
query ({ id, ...data }) {
return {
url: `/content-releases/${id}`,
method: 'PUT',
data
};
},
invalidatesTags: (result, error, arg)=>[
{
type: 'Release',
id: arg.id
}
]
}),
createReleaseAction: build.mutation({
query ({ body, params }) {
return {
url: `/content-releases/${params.releaseId}/actions`,
method: 'POST',
data: body
};
},
invalidatesTags: [
{
type: 'Release',
id: 'LIST'
},
{
type: 'ReleaseAction',
id: 'LIST'
}
]
}),
createManyReleaseActions: build.mutation({
query ({ body, params }) {
return {
url: `/content-releases/${params.releaseId}/actions/bulk`,
method: 'POST',
data: body
};
},
invalidatesTags: [
{
type: 'Release',
id: 'LIST'
},
{
type: 'ReleaseAction',
id: 'LIST'
},
{
type: 'EntriesInRelease'
}
]
}),
updateReleaseAction: build.mutation({
query ({ body, params }) {
return {
url: `/content-releases/${params.releaseId}/actions/${params.actionId}`,
method: 'PUT',
data: body
};
},
invalidatesTags: (res, error, arg)=>[
{
type: 'ReleaseAction',
id: 'LIST'
},
{
type: 'Release',
id: 'LIST'
},
{
type: 'Release',
id: arg.params.releaseId
}
],
async onQueryStarted ({ body, params, query, actionPath }, { dispatch, queryFulfilled }) {
// We need to mimic the same params received by the getReleaseActions query
const paramsWithoutActionId = {
releaseId: params.releaseId,
...query
};
const patchResult = dispatch(releaseApi.util.updateQueryData('getReleaseActions', paramsWithoutActionId, (draft)=>{
const [key, index] = actionPath;
const action = draft.data[key][index];
if (action) {
action.type = body.type;
}
}));
try {
await queryFulfilled;
} catch {
patchResult.undo();
}
}
}),
deleteReleaseAction: build.mutation({
query ({ params }) {
return {
url: `/content-releases/${params.releaseId}/actions/${params.actionId}`,
method: 'DELETE'
};
},
invalidatesTags: (result, error, arg)=>[
{
type: 'Release',
id: 'LIST'
},
{
type: 'Release',
id: arg.params.releaseId
},
{
type: 'ReleaseAction',
id: 'LIST'
},
{
type: 'EntriesInRelease'
}
]
}),
publishRelease: build.mutation({
query ({ id }) {
return {
url: `/content-releases/${id}/publish`,
method: 'POST'
};
},
invalidatesTags: (result, error, arg)=>[
{
type: 'Release',
id: arg.id
},
{
type: 'Document',
id: `ALL_LIST`
}
]
}),
deleteRelease: build.mutation({
query ({ id }) {
return {
url: `/content-releases/${id}`,
method: 'DELETE'
};
},
invalidatesTags: ()=>[
{
type: 'Release',
id: 'LIST'
},
{
type: 'EntriesInRelease'
}
]
}),
getMappedEntriesInReleases: build.query({
query (params) {
return {
url: '/content-releases/mapEntriesToReleases',
method: 'GET',
config: {
params
}
};
},
transformResponse (response) {
return response.data;
},
providesTags: [
{
type: 'EntriesInRelease'
}
]
}),
getReleaseSettings: build.query({
query: ()=>'/content-releases/settings',
providesTags: [
{
type: 'ReleaseSettings'
}
]
}),
updateReleaseSettings: build.mutation({
query (data) {
return {
url: '/content-releases/settings',
method: 'PUT',
data
};
},
invalidatesTags: [
{
type: 'ReleaseSettings'
}
]
})
};
}
});
const { useGetReleasesQuery, useGetReleasesForEntryQuery, useGetReleaseQuery, useGetReleaseActionsQuery, useCreateReleaseMutation, useCreateReleaseActionMutation, useCreateManyReleaseActionsMutation, useUpdateReleaseMutation, useUpdateReleaseActionMutation, usePublishReleaseMutation, useDeleteReleaseActionMutation, useDeleteReleaseMutation, useGetMappedEntriesInReleasesQuery, useGetReleaseSettingsQuery, useUpdateReleaseSettingsMutation } = releaseApi;
export { releaseApi, useCreateManyReleaseActionsMutation, useCreateReleaseActionMutation, useCreateReleaseMutation, useDeleteReleaseActionMutation, useDeleteReleaseMutation, useGetMappedEntriesInReleasesQuery, useGetReleaseActionsQuery, useGetReleaseQuery, useGetReleaseSettingsQuery, useGetReleasesForEntryQuery, useGetReleasesQuery, usePublishReleaseMutation, useUpdateReleaseActionMutation, useUpdateReleaseMutation, useUpdateReleaseSettingsMutation };
//# sourceMappingURL=release.mjs.map
@@ -0,0 +1 @@
{"version":3,"file":"release.mjs","sources":["../../../admin/src/services/release.ts"],"sourcesContent":["import { adminApi } from '@strapi/admin/strapi-admin';\n\nimport {\n CreateReleaseAction,\n CreateManyReleaseActions,\n DeleteReleaseAction,\n} from '../../../shared/contracts/release-actions';\n\nimport type {\n GetReleaseActions,\n UpdateReleaseAction,\n ReleaseActionGroupBy,\n} from '../../../shared/contracts/release-actions';\nimport type {\n CreateRelease,\n DeleteRelease,\n GetReleases,\n GetReleasesByDocumentAttached,\n UpdateRelease,\n GetRelease,\n PublishRelease,\n MapEntriesToReleases,\n} from '../../../shared/contracts/releases';\nimport type { GetSettings, UpdateSettings } from '../../../shared/contracts/settings';\nimport type { EndpointDefinition } from '@reduxjs/toolkit/query';\n\nexport interface GetReleasesQueryParams {\n page?: number;\n pageSize?: number;\n filters?: {\n releasedAt?: {\n // TODO: this should be a boolean, find a way to avoid strings\n $notNull?: boolean | 'true' | 'false';\n };\n };\n}\n\nexport interface GetReleaseActionsQueryParams {\n page?: number;\n pageSize?: number;\n groupBy?: ReleaseActionGroupBy;\n}\n\ntype GetReleasesTabResponse = GetReleases.Response & {\n meta: {\n activeTab: 'pending' | 'done';\n };\n};\n\ntype AnyEndpointDefinition = EndpointDefinition<any, any, any, any>;\n\n// TODO: move this into the admin code & expose an improved version of enhanceEndpoints or a new function\nconst extendInvalidatesTags = (\n endpoint: AnyEndpointDefinition,\n extraTags: string[] | { type: string; id: string }[]\n) => {\n if (!endpoint) {\n return;\n }\n\n const originalInvalidatesTags = endpoint.invalidatesTags;\n\n const newInvalidatesTags: AnyEndpointDefinition['invalidatesTags'] = (\n result,\n err,\n args,\n meta\n ) => {\n const originalTags =\n typeof originalInvalidatesTags === 'function'\n ? originalInvalidatesTags(result, err, args, meta)\n : originalInvalidatesTags;\n\n return [...(originalTags ?? []), ...extraTags];\n };\n\n Object.assign(endpoint, { invalidatesTags: newInvalidatesTags });\n};\n\nconst releaseApi = adminApi\n .enhanceEndpoints({\n addTagTypes: ['Release', 'ReleaseAction', 'EntriesInRelease', 'ReleaseSettings', 'Document'],\n endpoints: {\n updateDocument(endpoint: AnyEndpointDefinition) {\n extendInvalidatesTags(endpoint, [\n { type: 'Release', id: 'LIST' },\n { type: 'ReleaseAction', id: 'LIST' },\n ]);\n },\n deleteDocument(endpoint: AnyEndpointDefinition) {\n extendInvalidatesTags(endpoint, [\n { type: 'Release', id: 'LIST' },\n { type: 'ReleaseAction', id: 'LIST' },\n ]);\n },\n deleteManyDocuments(endpoint: AnyEndpointDefinition) {\n extendInvalidatesTags(endpoint, [\n { type: 'Release', id: 'LIST' },\n { type: 'ReleaseAction', id: 'LIST' },\n ]);\n },\n discardDocument(endpoint: AnyEndpointDefinition) {\n extendInvalidatesTags(endpoint, [\n { type: 'Release', id: 'LIST' },\n { type: 'ReleaseAction', id: 'LIST' },\n ]);\n },\n createWorkflow(endpoint: AnyEndpointDefinition) {\n extendInvalidatesTags(endpoint, [\n { type: 'Release', id: 'LIST' },\n { type: 'ReleaseAction', id: 'LIST' },\n ]);\n },\n updateWorkflow(endpoint: AnyEndpointDefinition) {\n extendInvalidatesTags(endpoint, [\n { type: 'Release', id: 'LIST' },\n { type: 'ReleaseAction', id: 'LIST' },\n ]);\n },\n deleteWorkflow(endpoint: AnyEndpointDefinition) {\n extendInvalidatesTags(endpoint, [\n { type: 'Release', id: 'LIST' },\n { type: 'ReleaseAction', id: 'LIST' },\n ]);\n },\n },\n })\n .injectEndpoints({\n endpoints: (build) => {\n return {\n getReleasesForEntry: build.query<\n GetReleasesByDocumentAttached.Response,\n Partial<GetReleasesByDocumentAttached.Request['query']>\n >({\n query(params) {\n return {\n url: '/content-releases/getByDocumentAttached',\n method: 'GET',\n config: {\n params,\n },\n };\n },\n providesTags: (result) =>\n result\n ? [\n ...result.data.map(({ id }) => ({ type: 'Release' as const, id })),\n { type: 'Release', id: 'LIST' },\n ]\n : [],\n }),\n getReleases: build.query<GetReleasesTabResponse, GetReleasesQueryParams | void>({\n query(\n { page, pageSize, filters } = {\n page: 1,\n pageSize: 16,\n filters: {\n releasedAt: {\n $notNull: false,\n },\n },\n }\n Line truncated
@@ -0,0 +1,13 @@
import type { ReleaseAction, ReleaseActionEntry, Stage } from '../../../shared/contracts/release-actions';
import type { Struct } from '@strapi/types';
interface EntryValidationPopoverProps {
action: ReleaseAction['type'];
schema?: Struct.ContentTypeSchema & {
hasReviewWorkflow: boolean;
stageRequiredToPublish?: Stage;
};
entry: ReleaseActionEntry;
status: ReleaseAction['status'];
}
export declare const EntryValidationPopover: ({ schema, entry, status, action, }: EntryValidationPopoverProps) => import("react/jsx-runtime").JSX.Element | null;
export {};
@@ -0,0 +1,28 @@
import * as React from 'react';
import { Duration } from 'date-fns';
interface CustomInterval {
unit: keyof Duration;
text: string;
threshold: number;
}
interface RelativeTimeProps extends React.ComponentPropsWithoutRef<'time'> {
timestamp: Date;
customIntervals?: CustomInterval[];
}
/**
* Displays the relative time between a given timestamp and the current time.
* You can display a custom message for given time intervals by passing an array of custom intervals.
*
* @example
* ```jsx
* <caption>Display "last hour" if the timestamp is less than an hour ago</caption>
* <RelativeTime
* timestamp={new Date('2021-01-01')}
* customIntervals={[
* { unit: 'hours', threshold: 1, text: 'last hour' },
* ]}
* ```
*/
declare const RelativeTime: React.ForwardRefExoticComponent<RelativeTimeProps & React.RefAttributes<HTMLTimeElement>>;
export { RelativeTime };
export type { CustomInterval, RelativeTimeProps };
@@ -0,0 +1,3 @@
import type { BulkActionComponent } from '@strapi/content-manager/strapi-admin';
declare const ReleaseAction: BulkActionComponent;
export { ReleaseAction };
@@ -0,0 +1,26 @@
import * as React from 'react';
import { DeleteReleaseAction, ReleaseAction } from '../../../shared/contracts/release-actions';
import { Release } from '../../../shared/contracts/releases';
interface DeleteReleaseActionItemProps {
releaseId: DeleteReleaseAction.Request['params']['releaseId'];
actionId: DeleteReleaseAction.Request['params']['actionId'];
}
interface ReleaseActionEntryLinkItemProps {
contentTypeUid: ReleaseAction['contentType'];
documentId: ReleaseAction['entry']['documentId'];
locale: ReleaseAction['locale'];
}
interface EditReleaseItemProps {
releaseId: Release['id'];
}
interface RootProps {
children: React.ReactNode;
hasTriggerBorder?: boolean;
}
export declare const ReleaseActionMenu: {
Root: ({ children }: RootProps) => import("react/jsx-runtime").JSX.Element | null;
EditReleaseItem: ({ releaseId }: EditReleaseItemProps) => import("react/jsx-runtime").JSX.Element;
DeleteReleaseActionItem: ({ releaseId, actionId }: DeleteReleaseActionItemProps) => import("react/jsx-runtime").JSX.Element | null;
ReleaseActionEntryLinkItem: ({ contentTypeUid, documentId, locale, }: ReleaseActionEntryLinkItemProps) => import("react/jsx-runtime").JSX.Element | null;
};
export {};
@@ -0,0 +1,24 @@
import * as yup from 'yup';
import { CreateReleaseAction } from '../../../shared/contracts/release-actions';
import type { DocumentActionComponent } from '@strapi/content-manager/strapi-admin';
export declare const RELEASE_ACTION_FORM_SCHEMA: yup.default<import("yup/lib/object").Assign<import("yup/lib/object").ObjectShape, {
type: import("yup/lib/string").RequiredStringSchema<string | undefined, Record<string, any>>;
releaseId: import("yup/lib/string").RequiredStringSchema<string | undefined, Record<string, any>>;
}>, Record<string, any>, import("yup/lib/object").TypeOfShape<import("yup/lib/object").Assign<import("yup/lib/object").ObjectShape, {
type: import("yup/lib/string").RequiredStringSchema<string | undefined, Record<string, any>>;
releaseId: import("yup/lib/string").RequiredStringSchema<string | undefined, Record<string, any>>;
}>>, import("yup/lib/object").AssertsShape<import("yup/lib/object").Assign<import("yup/lib/object").ObjectShape, {
type: import("yup/lib/string").RequiredStringSchema<string | undefined, Record<string, any>>;
releaseId: import("yup/lib/string").RequiredStringSchema<string | undefined, Record<string, any>>;
}>>>;
export interface FormValues {
type: CreateReleaseAction.Request['body']['type'];
releaseId: CreateReleaseAction.Request['params']['releaseId'];
}
export declare const INITIAL_VALUES: {
type: "publish";
releaseId: string;
};
export declare const NoReleases: () => import("react/jsx-runtime").JSX.Element;
declare const ReleaseActionModalForm: DocumentActionComponent;
export { ReleaseActionModalForm };
@@ -0,0 +1,9 @@
import * as React from 'react';
interface ActionOptionProps {
selected: 'publish' | 'unpublish';
handleChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
name: string;
disabled?: boolean;
}
export declare const ReleaseActionOptions: ({ selected, handleChange, name, disabled, }: ActionOptionProps) => import("react/jsx-runtime").JSX.Element;
export {};
@@ -0,0 +1,28 @@
import { ListFieldLayout, ListLayout } from '@strapi/content-manager/strapi-admin';
import type { Modules, UID } from '@strapi/types';
interface AddColumnToTableHookArgs {
layout: ListLayout;
displayedHeaders: ListFieldLayout[];
}
declare const addColumnToTableHook: ({ displayedHeaders, layout }: AddColumnToTableHookArgs) => {
displayedHeaders: (ListFieldLayout | {
searchable: boolean;
sortable: boolean;
name: string;
label: {
id: string;
defaultMessage: string;
};
cellFormatter: (props: Modules.Documents.AnyDocument, _: any, { model }: {
model: UID.ContentType;
}) => import("react/jsx-runtime").JSX.Element;
})[];
layout: ListLayout;
};
interface ReleaseListCellProps extends Modules.Documents.AnyDocument {
documentId: Modules.Documents.ID;
model: UID.ContentType;
}
declare const ReleaseListCell: ({ documentId, model }: ReleaseListCellProps) => import("react/jsx-runtime").JSX.Element;
export { ReleaseListCell, addColumnToTableHook };
export type { ReleaseListCellProps };
@@ -0,0 +1,17 @@
export interface FormValues {
name: string;
date?: string;
time: string;
timezone: string | null;
isScheduled?: boolean;
scheduledAt: Date | null;
}
interface ReleaseModalProps {
handleClose: () => void;
handleSubmit: (values: FormValues) => void;
isLoading?: boolean;
initialValues: FormValues;
open?: boolean;
}
export declare const ReleaseModal: ({ handleClose, open, handleSubmit, initialValues, isLoading, }: ReleaseModalProps) => import("react/jsx-runtime").JSX.Element;
export {};
@@ -0,0 +1,3 @@
import type { PanelComponent } from '@strapi/content-manager/strapi-admin';
declare const Panel: PanelComponent;
export { Panel };
@@ -0,0 +1,76 @@
export declare const PERMISSIONS: {
main: {
action: string;
subject: null;
id: string;
actionParameters: {};
properties: {};
conditions: never[];
}[];
create: {
action: string;
subject: null;
id: string;
actionParameters: {};
properties: {};
conditions: never[];
}[];
update: {
action: string;
subject: null;
id: string;
actionParameters: {};
properties: {};
conditions: never[];
}[];
delete: {
action: string;
subject: null;
id: string;
actionParameters: {};
properties: {};
conditions: never[];
}[];
createAction: {
action: string;
subject: null;
id: string;
actionParameters: {};
properties: {};
conditions: never[];
}[];
deleteAction: {
action: string;
subject: null;
id: string;
actionParameters: {};
properties: {};
conditions: never[];
}[];
publish: {
action: string;
subject: null;
id: string;
actionParameters: {};
properties: {};
conditions: never[];
}[];
};
export declare const PERMISSIONS_SETTINGS: {
read: {
action: string;
subject: null;
id: string;
actionParameters: {};
properties: {};
conditions: never[];
}[];
update: {
action: string;
subject: null;
id: string;
actionParameters: {};
properties: {};
conditions: never[];
}[];
};
@@ -0,0 +1,3 @@
import type { Plugin } from '@strapi/types';
declare const admin: Plugin.Config.AdminInput;
export default admin;
@@ -0,0 +1,7 @@
import { Dispatch } from '@reduxjs/toolkit';
import { TypedUseSelectorHook } from 'react-redux';
import type { Store } from '@strapi/admin/strapi-admin';
type RootState = ReturnType<Store['getState']>;
declare const useTypedDispatch: () => Dispatch;
declare const useTypedSelector: TypedUseSelectorHook<RootState>;
export { useTypedSelector, useTypedDispatch };
@@ -0,0 +1 @@
export declare const App: () => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,2 @@
declare const PurchaseContentReleases: () => import("react/jsx-runtime").JSX.Element;
export { PurchaseContentReleases };
@@ -0,0 +1,2 @@
declare const ReleaseDetailsPage: () => import("react/jsx-runtime").JSX.Element;
export { ReleaseDetailsPage };
@@ -0,0 +1,8 @@
import { type Release } from '../../../shared/contracts/releases';
declare const getBadgeProps: (status: Release['status']) => {
textColor: string;
backgroundColor: string;
borderColor: string;
};
declare const ReleasesPage: () => import("react/jsx-runtime").JSX.Element;
export { ReleasesPage, getBadgeProps };
Loaded 100 of 65041 files, more files were not shown because too many files have changed in this diff. Show more