Browse Source

feat: add audio and language modals, update audio context, and enhance UI configuration

master
sina_sajjadi 3 weeks ago
parent
commit
dd2b5d732d
  1. 7
      next.config.ts
  2. BIN
      public/assets/images/Group 27010.png
  3. 4
      public/assets/images/Group 27010.svg
  4. 4
      public/assets/images/Group 270S10.svg
  5. 3
      public/assets/images/Group 852.svg
  6. 3
      public/assets/images/VectAAAAAAAAAor.svg
  7. BIN
      public/assets/images/VectSSSSSSSor.jpg
  8. 3
      public/assets/images/VectodewsqaDr.svg
  9. 3
      public/assets/images/VectorDua.svg
  10. 8
      src/components/context/audio-conext.tsx
  11. 12
      src/components/context/ui.context.tsx
  12. 127
      src/components/language-switcher.tsx
  13. 2
      src/components/layout/header.tsx
  14. 89
      src/components/modals/audio-setting.tsx
  15. 92
      src/components/modals/languages-modal.tsx
  16. 4
      src/components/modals/modal-manager.tsx
  17. 40
      src/components/modals/reciters.tsx
  18. 28
      src/components/sticky-components/audio-controls.tsx
  19. 48
      src/components/utils/hooks/local-storage.tsx

7
next.config.ts

@ -7,7 +7,12 @@ const nextConfig: NextConfig = {
images: { images: {
domains: ["habibapp.com"], // Add the domain for image hosting domains: ["habibapp.com"], // Add the domain for image hosting
}, },
typescript : {
ignoreBuildErrors : true
},
eslint : {
ignoreDuringBuilds : true
}
// Add other Next.js config options here as needed // Add other Next.js config options here as needed
}; };

BIN
public/assets/images/Group 27010.png

After

Width: 353  |  Height: 50  |  Size: 2.0 KiB

4
public/assets/images/Group 27010.svg

@ -0,0 +1,4 @@
<svg width="353" height="50" viewBox="0 0 353 50" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M236.237 20.9152L236.229 20.9089C236.216 20.8982 236.193 20.88 236.162 20.8548C236.1 20.8044 236.006 20.726 235.887 20.6229C235.649 20.4156 235.318 20.1142 234.958 19.744C234.221 18.9861 233.49 18.0722 233.081 17.1812C231.63 13.455 228.892 11.8496 226.942 11.1639C226.755 10.6079 226.474 10.0036 226.05 9.41273C225.2 8.22904 223.917 7.29362 222.153 6.83367C221.977 6.44025 221.745 6.02682 221.442 5.61479M236.237 20.9152L83.9957 2.50227L83.9966 5.00227L83.9966 2.50227C90.3352 2.5 211.524 2.5 214.128 2.5L214.136 2.5C217.942 2.5 220.218 3.94951 221.442 5.61479M236.237 20.9152L236.242 20.9193L236.245 20.9209L236.573 21.1736L239.152 23.159L236.569 25.139L236.245 25.3873L236.244 25.3875L236.243 25.3886L236.233 25.3962L236.23 25.3991C236.224 25.4036 236.217 25.4094 236.208 25.4164C236.196 25.4263 236.18 25.4386 236.162 25.4533C236.1 25.5038 236.006 25.5823 235.887 25.6854C235.648 25.8928 235.318 26.194 234.958 26.5636C234.221 27.3207 233.493 28.23 233.088 29.1113C231.634 32.8467 228.895 34.4563 226.943 35.1437C226.757 35.6999 226.475 36.3045 226.051 36.8957C225.201 38.0797 223.917 39.0152 222.152 39.475C221.873 40.0976 221.459 40.7709 220.843 41.4058C219.415 42.8758 217.226 43.8082 214.137 43.8082C211.768 43.8082 90.3428 43.8082 83.9978 43.806C83.9975 43.806 83.9972 43.806 83.9969 43.806L79.8228 43.806L236.237 20.9152ZM221.442 5.61479L219.428 7.09638L221.443 5.61585C221.443 5.6155 221.442 5.61514 221.442 5.61479ZM21.8974 39.475C22.1772 40.1009 22.5939 40.7774 23.2148 41.4144C24.6441 42.8807 26.8312 43.8082 29.9122 43.8082C32.2817 43.8082 73.4845 43.8082 79.8219 43.806L21.8974 39.475ZM21.8974 39.475C20.1321 39.0153 18.8485 38.0798 17.9983 36.896C17.5738 36.3049 17.2924 35.7005 17.1058 35.1446C15.1552 34.4591 12.417 32.8538 10.9669 29.1269C10.558 28.2357 9.82669 27.3216 9.08967 26.5637C8.48656 25.9435 7.98148 25.5309 7.86904 25.4391C7.84404 25.4187 7.83845 25.4141 7.85552 25.4266L7.833 25.4101L7.81085 25.3931L7.47517 25.1347L4.89552 23.1492L7.47938 21.1692L7.80511 20.9196L7.80651 20.9185L7.81842 20.9091C7.83196 20.8984 7.85471 20.8802 7.88567 20.855C7.94768 20.8044 8.0421 20.7259 8.16085 20.6228C8.39962 20.4154 8.72991 20.1142 9.08975 19.7446C9.82683 18.9876 10.555 18.0782 10.9594 17.1969C12.4138 13.4613 15.1522 11.8517 17.1047 11.1643C17.2912 10.6082 17.5726 10.0036 17.997 9.41249C18.8472 8.2285 20.1308 7.29303 21.896 6.83327C22.1744 6.21067 22.5886 5.53729 23.2054 4.90241C24.6334 3.43242 26.8224 2.5 29.911 2.5C32.2804 2.5 73.4832 2.5 79.8207 2.50227C79.821 2.50227 79.8213 2.50227 79.8216 2.50227L21.8974 39.475Z" fill="#F4846F" stroke="#F5F5F5" stroke-width="5"/>
<path d="M350.944 26.3042C350.911 26.2766 347.92 23.7688 346.672 20.752C345.038 16.1475 341.565 14.8569 339.975 14.5038C339.902 13.8266 339.683 12.8001 339.043 11.8375C338.288 10.7011 336.98 9.70442 334.785 9.4413C334.675 8.86092 334.432 8.05615 333.889 7.25922C333.889 7.25915 333.889 7.25908 333.889 7.25901L333.476 7.54049L350.944 26.3042ZM350.944 26.3042L351.32 26.6162L351.783 27.0015L351.319 27.3859L350.944 27.6958C350.944 27.6958 350.944 27.6959 350.944 27.696C350.926 27.7114 350.17 28.3452 349.261 29.3531C348.343 30.372 347.294 31.746 346.68 33.2288C345.041 37.848 341.568 39.1424 339.977 39.4961C339.904 40.1733 339.684 41.1999 339.044 42.1625C338.289 43.2991 336.98 44.2959 334.784 44.5588C334.646 45.2812 334.299 46.3665 333.423 47.3416C332.347 48.5373 330.536 49.5 327.539 49.5L32.462 49.5C29.4727 49.5 27.662 48.5421 26.5853 47.3489C25.7054 46.3739 25.3564 45.2865 25.2174 44.5588C23.0216 44.2959 21.7129 43.2991 20.9573 42.1626C20.3171 41.1998 20.0974 40.1732 20.0236 39.4963C18.4337 39.1433 14.9613 37.8529 13.3284 33.248C12.7106 31.7546 11.6591 30.3754 10.7398 29.3543C9.85095 28.367 9.11119 27.7424 9.0678 27.7058C9.06662 27.7048 9.06596 27.7042 9.06581 27.7041L9.06574 27.7042L9.05697 27.6969L8.68035 27.3838L8.21687 26.9985L8.68119 26.6141L9.05554 26.3043L9.05566 26.3042C9.05584 26.3041 9.05618 26.3038 9.05668 26.3033C9.09012 26.2753 9.83997 25.6442 10.7389 24.6469C11.6574 23.628 12.7065 22.254 13.32 20.7712C14.9591 16.1518 18.4315 14.8575 20.0232 14.5038C20.0965 13.8266 20.3161 12.8001 20.956 11.8375C21.7115 10.7009 23.0201 9.70413 25.2158 9.44119L350.944 26.3042Z" fill="#F4846F" stroke="#EB6E57"/>
</svg>

4
public/assets/images/Group 270S10.svg

@ -0,0 +1,4 @@
<svg width="353" height="50" viewBox="0 0 353 50" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M236.237 20.9152L236.229 20.9089C236.216 20.8982 236.193 20.88 236.162 20.8548C236.1 20.8044 236.006 20.726 235.887 20.6229C235.649 20.4156 235.318 20.1142 234.958 19.744C234.221 18.9861 233.49 18.0722 233.081 17.1812C231.63 13.455 228.892 11.8496 226.942 11.1639C226.755 10.6079 226.474 10.0036 226.05 9.41273C225.2 8.22904 223.917 7.29362 222.153 6.83367C221.977 6.44025 221.745 6.02682 221.442 5.61479M236.237 20.9152L83.9957 2.50227L83.9966 5.00227L83.9966 2.50227C90.3352 2.5 211.524 2.5 214.128 2.5L214.136 2.5C217.942 2.5 220.218 3.94951 221.442 5.61479M236.237 20.9152L236.242 20.9193L236.245 20.9209L236.573 21.1736L239.152 23.159L236.569 25.139L236.245 25.3873L236.244 25.3875L236.243 25.3886L236.233 25.3962L236.23 25.3991C236.224 25.4036 236.217 25.4094 236.208 25.4164C236.196 25.4263 236.18 25.4386 236.162 25.4533C236.1 25.5038 236.006 25.5823 235.887 25.6854C235.648 25.8928 235.318 26.194 234.958 26.5636C234.221 27.3207 233.493 28.23 233.088 29.1113C231.634 32.8467 228.895 34.4563 226.943 35.1437C226.757 35.6999 226.475 36.3045 226.051 36.8957C225.201 38.0797 223.917 39.0152 222.152 39.475C221.873 40.0976 221.459 40.7709 220.843 41.4058C219.415 42.8758 217.226 43.8082 214.137 43.8082C211.768 43.8082 90.3428 43.8082 83.9978 43.806C83.9975 43.806 83.9972 43.806 83.9969 43.806L79.8228 43.806L236.237 20.9152ZM221.442 5.61479L219.428 7.09638L221.443 5.61585C221.443 5.6155 221.442 5.61514 221.442 5.61479ZM21.8974 39.475C22.1772 40.1009 22.5939 40.7774 23.2148 41.4144C24.6441 42.8807 26.8312 43.8082 29.9122 43.8082C32.2817 43.8082 73.4845 43.8082 79.8219 43.806L21.8974 39.475ZM21.8974 39.475C20.1321 39.0153 18.8485 38.0798 17.9983 36.896C17.5738 36.3049 17.2924 35.7005 17.1058 35.1446C15.1552 34.4591 12.417 32.8538 10.9669 29.1269C10.558 28.2357 9.82669 27.3216 9.08967 26.5637C8.48656 25.9435 7.98148 25.5309 7.86904 25.4391C7.84404 25.4187 7.83845 25.4141 7.85552 25.4266L7.833 25.4101L7.81085 25.3931L7.47517 25.1347L4.89552 23.1492L7.47938 21.1692L7.80511 20.9196L7.80651 20.9185L7.81842 20.9091C7.83196 20.8984 7.85471 20.8802 7.88567 20.855C7.94768 20.8044 8.0421 20.7259 8.16085 20.6228C8.39962 20.4154 8.72991 20.1142 9.08975 19.7446C9.82683 18.9876 10.555 18.0782 10.9594 17.1969C12.4138 13.4613 15.1522 11.8517 17.1047 11.1643C17.2912 10.6082 17.5726 10.0036 17.997 9.41249C18.8472 8.2285 20.1308 7.29303 21.896 6.83327C22.1744 6.21067 22.5886 5.53729 23.2054 4.90241C24.6334 3.43242 26.8224 2.5 29.911 2.5C32.2804 2.5 73.4832 2.5 79.8207 2.50227C79.821 2.50227 79.8213 2.50227 79.8216 2.50227L21.8974 39.475Z" fill="#F4846F" stroke="#F5F5F5" stroke-width="5"/>
<path d="M350.944 26.3042C350.911 26.2766 347.92 23.7688 346.672 20.752C345.038 16.1475 341.565 14.8569 339.975 14.5038C339.902 13.8266 339.683 12.8001 339.043 11.8375C338.288 10.7011 336.98 9.70442 334.785 9.4413C334.675 8.86092 334.432 8.05615 333.889 7.25922C333.889 7.25915 333.889 7.25908 333.889 7.25901L333.476 7.54049L350.944 26.3042ZM350.944 26.3042L351.32 26.6162L351.783 27.0015L351.319 27.3859L350.944 27.6958C350.944 27.6958 350.944 27.6959 350.944 27.696C350.926 27.7114 350.17 28.3452 349.261 29.3531C348.343 30.372 347.294 31.746 346.68 33.2288C345.041 37.848 341.568 39.1424 339.977 39.4961C339.904 40.1733 339.684 41.1999 339.044 42.1625C338.289 43.2991 336.98 44.2959 334.784 44.5588C334.646 45.2812 334.299 46.3665 333.423 47.3416C332.347 48.5373 330.536 49.5 327.539 49.5L32.462 49.5C29.4727 49.5 27.662 48.5421 26.5853 47.3489C25.7054 46.3739 25.3564 45.2865 25.2174 44.5588C23.0216 44.2959 21.7129 43.2991 20.9573 42.1626C20.3171 41.1998 20.0974 40.1732 20.0236 39.4963C18.4337 39.1433 14.9613 37.8529 13.3284 33.248C12.7106 31.7546 11.6591 30.3754 10.7398 29.3543C9.85095 28.367 9.11119 27.7424 9.0678 27.7058C9.06662 27.7048 9.06596 27.7042 9.06581 27.7041L9.06574 27.7042L9.05697 27.6969L8.68035 27.3838L8.21687 26.9985L8.68119 26.6141L9.05554 26.3043L9.05566 26.3042C9.05584 26.3041 9.05618 26.3038 9.05668 26.3033C9.09012 26.2753 9.83997 25.6442 10.7389 24.6469C11.6574 23.628 12.7065 22.254 13.32 20.7712C14.9591 16.1518 18.4315 14.8575 20.0232 14.5038C20.0965 13.8266 20.3161 12.8001 20.956 11.8375C21.7115 10.7009 23.0201 9.70413 25.2158 9.44119L350.944 26.3042Z" fill="#F4846F" stroke="#EB6E57"/>
</svg>

3
public/assets/images/Group 852.svg

@ -0,0 +1,3 @@
<svg width="17" height="11" viewBox="0 0 17 11" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.15515 8.94383C6.78415 8.57255 6.59864 8.12469 6.59864 7.60027C6.59864 7.07586 6.78668 6.62561 7.16275 6.24954L15.238 0.875721L9.84945 8.95101C9.48323 9.31723 9.03538 9.50034 8.50589 9.50034C7.9764 9.50034 7.52616 9.31484 7.15515 8.94383ZM8.49871 0.920478C7.59822 0.920478 6.73475 1.09866 5.90828 1.45503C5.08183 1.8114 4.37176 2.28628 3.7781 2.87966C3.18443 3.47304 2.71195 4.18071 2.36065 5.00267C2.00934 5.82463 1.83369 6.68557 1.83369 7.5855C1.83369 8.63462 2.07127 9.63419 2.54643 10.5842L1.71504 10.9997C1.17064 9.92103 0.898438 8.78789 0.898438 7.60027C0.898438 6.56129 1.09886 5.5741 1.4997 4.6387C1.90055 3.7033 2.43989 2.89683 3.11772 2.21928C3.79555 1.54173 4.60456 1.00239 5.54474 0.601267C6.48492 0.200141 7.46958 -0.000281175 8.49871 2.96056e-07C10.092 2.96056e-07 11.5418 0.455172 12.8482 1.36552L12.002 1.92963C10.9233 1.25686 9.75557 0.920478 8.49871 0.920478ZM14.7482 3.26601C15.6487 4.57241 16.099 6.01731 16.099 7.6007C16.099 8.78831 15.8268 9.92146 15.2824 11.0001L14.451 10.5694C14.9262 9.62927 15.1637 8.63475 15.1637 7.58592C15.1637 6.34876 14.8372 5.19085 14.1841 4.11217L14.7482 3.26601Z" fill="white"/>
</svg>

3
public/assets/images/VectAAAAAAAAAor.svg

@ -0,0 +1,3 @@
<svg width="244" height="47" viewBox="0 0 244 47" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M236.237 20.9152L236.229 20.9089C236.216 20.8982 236.193 20.88 236.162 20.8548C236.1 20.8044 236.006 20.726 235.887 20.6229C235.649 20.4156 235.318 20.1142 234.958 19.744C234.221 18.9861 233.49 18.0722 233.081 17.1812C231.63 13.455 228.892 11.8496 226.942 11.1639C226.755 10.6079 226.474 10.0036 226.05 9.41273C225.2 8.22904 223.917 7.29362 222.153 6.83367C221.977 6.44025 221.745 6.02682 221.442 5.61479M236.237 20.9152L83.9957 2.50227L83.9966 5.00227L83.9966 2.50227C90.3352 2.5 211.524 2.5 214.128 2.5L214.136 2.5C217.942 2.5 220.218 3.94951 221.442 5.61479M236.237 20.9152L236.242 20.9193L236.245 20.9209L236.573 21.1736L239.152 23.159L236.569 25.139L236.245 25.3873L236.244 25.3875L236.243 25.3886L236.233 25.3962L236.23 25.3991C236.224 25.4036 236.217 25.4094 236.208 25.4164C236.196 25.4263 236.18 25.4386 236.162 25.4533C236.1 25.5038 236.006 25.5823 235.887 25.6854C235.648 25.8928 235.318 26.194 234.958 26.5636C234.221 27.3207 233.493 28.23 233.088 29.1113C231.634 32.8467 228.895 34.4563 226.943 35.1437C226.757 35.6999 226.475 36.3045 226.051 36.8957C225.201 38.0797 223.917 39.0152 222.152 39.475C221.873 40.0976 221.459 40.7709 220.843 41.4058C219.415 42.8758 217.226 43.8082 214.137 43.8082C211.768 43.8082 90.3428 43.8082 83.9978 43.806C83.9975 43.806 83.9972 43.806 83.9969 43.806L79.8228 43.806L236.237 20.9152ZM221.442 5.61479L219.428 7.09638L221.443 5.61585C221.443 5.6155 221.442 5.61514 221.442 5.61479ZM21.8974 39.475C22.1772 40.1009 22.5939 40.7774 23.2148 41.4144C24.6441 42.8807 26.8312 43.8082 29.9122 43.8082C32.2817 43.8082 73.4845 43.8082 79.8219 43.806L21.8974 39.475ZM21.8974 39.475C20.1321 39.0153 18.8485 38.0798 17.9983 36.896C17.5738 36.3049 17.2924 35.7005 17.1058 35.1446C15.1552 34.4591 12.417 32.8538 10.9669 29.1269C10.558 28.2357 9.82669 27.3216 9.08967 26.5637C8.48656 25.9435 7.98148 25.5309 7.86904 25.4391C7.84404 25.4187 7.83845 25.4141 7.85552 25.4266L7.833 25.4101L7.81085 25.3931L7.47517 25.1347L4.89552 23.1492L7.47938 21.1692L7.80511 20.9196L7.80651 20.9185L7.81842 20.9091C7.83196 20.8984 7.85471 20.8802 7.88567 20.855C7.94768 20.8044 8.0421 20.7259 8.16085 20.6228C8.39962 20.4154 8.72991 20.1142 9.08975 19.7446C9.82683 18.9876 10.555 18.0782 10.9594 17.1969C12.4138 13.4613 15.1522 11.8517 17.1047 11.1643C17.2912 10.6082 17.5726 10.0036 17.997 9.41249C18.8472 8.2285 20.1308 7.29303 21.896 6.83327C22.1744 6.21067 22.5886 5.53729 23.2054 4.90241C24.6334 3.43242 26.8224 2.5 29.911 2.5C32.2804 2.5 73.4832 2.5 79.8207 2.50227C79.821 2.50227 79.8213 2.50227 79.8216 2.50227L21.8974 39.475Z" fill="#F4846F" stroke="#F5F5F5" stroke-width="5"/>
</svg>

BIN
public/assets/images/VectSSSSSSSor.jpg

After

Width: 244  |  Height: 47  |  Size: 6.2 KiB

3
public/assets/images/VectodewsqaDr.svg

@ -0,0 +1,3 @@
<svg width="346" height="46" viewBox="0 0 346 46" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M343.944 22.3042C343.911 22.2766 340.92 19.7688 339.672 16.752C338.038 12.1475 334.565 10.8569 332.975 10.5038C332.902 9.82663 332.683 8.80014 332.043 7.83749C331.288 6.70112 329.98 5.70442 327.785 5.4413C327.675 4.86092 327.432 4.05615 326.889 3.25922C326.889 3.25915 326.889 3.25908 326.889 3.25901L326.476 3.54049L343.944 22.3042ZM343.944 22.3042L344.32 22.6162L344.783 23.0015L344.319 23.3859L343.944 23.6958C343.944 23.6958 343.944 23.6959 343.944 23.696C343.926 23.7114 343.17 24.3452 342.261 25.3531C341.343 26.372 340.294 27.746 339.68 29.2288C338.041 33.848 334.568 35.1424 332.977 35.4961C332.904 36.1733 332.684 37.1999 332.044 38.1625C331.289 39.2991 329.98 40.2959 327.784 40.5588C327.646 41.2812 327.299 42.3665 326.423 43.3416C325.347 44.5373 323.536 45.5 320.539 45.5L25.462 45.5C22.4727 45.5 20.662 44.5421 19.5853 43.3489C18.7054 42.3739 18.3564 41.2865 18.2174 40.5588C16.0216 40.2959 14.7129 39.2991 13.9573 38.1626C13.3171 37.1998 13.0974 36.1732 13.0236 35.4963C11.4337 35.1433 7.96127 33.8529 6.32844 29.248C5.71058 27.7546 4.65913 26.3754 3.7398 25.3543C2.85095 24.367 2.11119 23.7424 2.0678 23.7058C2.06662 23.7048 2.06596 23.7042 2.06581 23.7041L2.06574 23.7042L2.05697 23.6969L1.68035 23.3838L1.21687 22.9985L1.68119 22.6141L2.05554 22.3043L2.05566 22.3042C2.05584 22.3041 2.05618 22.3038 2.05668 22.3033C2.09012 22.2753 2.83997 21.6442 3.73893 20.6469C4.65739 19.628 5.70647 18.254 6.31997 16.7712C7.95911 12.1518 11.4315 10.8575 13.0232 10.5038C13.0965 9.82662 13.3161 8.80012 13.956 7.83745C14.7115 6.70092 16.0201 5.70413 18.2158 5.44119L343.944 22.3042Z" fill="#F4846F" stroke="#EB6E57"/>
</svg>

3
public/assets/images/VectorDua.svg

@ -0,0 +1,3 @@
<svg width="346" height="46" viewBox="0 0 346 46" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M343.944 22.3042C343.911 22.2766 340.92 19.7688 339.672 16.752C338.038 12.1475 334.565 10.8569 332.975 10.5038C332.902 9.82663 332.683 8.80014 332.043 7.83749C331.288 6.70112 329.98 5.70442 327.785 5.4413C327.675 4.86092 327.432 4.05615 326.889 3.25922C326.889 3.25915 326.889 3.25908 326.889 3.25901L326.476 3.54049L343.944 22.3042ZM343.944 22.3042L344.32 22.6162L344.783 23.0015L344.319 23.3859L343.944 23.6958C343.944 23.6958 343.944 23.6959 343.944 23.696C343.926 23.7114 343.17 24.3452 342.261 25.3531C341.343 26.372 340.294 27.746 339.68 29.2288C338.041 33.848 334.568 35.1424 332.977 35.4961C332.904 36.1733 332.684 37.1999 332.044 38.1625C331.289 39.2991 329.98 40.2959 327.784 40.5588C327.646 41.2812 327.299 42.3665 326.423 43.3416C325.347 44.5373 323.536 45.5 320.539 45.5L25.462 45.5C22.4727 45.5 20.662 44.5421 19.5853 43.3489C18.7054 42.3739 18.3564 41.2865 18.2174 40.5588C16.0216 40.2959 14.7129 39.2991 13.9573 38.1626C13.3171 37.1998 13.0974 36.1732 13.0236 35.4963C11.4337 35.1433 7.96127 33.8529 6.32844 29.248C5.71058 27.7546 4.65913 26.3754 3.7398 25.3543C2.85095 24.367 2.11119 23.7424 2.0678 23.7058C2.06662 23.7048 2.06596 23.7042 2.06581 23.7041L2.06574 23.7042L2.05697 23.6969L1.68035 23.3838L1.21687 22.9985L1.68119 22.6141L2.05554 22.3043L2.05566 22.3042C2.05584 22.3041 2.05618 22.3038 2.05668 22.3033C2.09012 22.2753 2.83997 21.6442 3.73893 20.6469C4.65739 19.628 5.70647 18.254 6.31997 16.7712C7.95911 12.1518 11.4315 10.8575 13.0232 10.5038C13.0965 9.82662 13.3161 8.80012 13.956 7.83745C14.7115 6.70092 16.0201 5.70413 18.2158 5.44119L343.944 22.3042Z" fill="#F4846F" stroke="#EB6E57"/>
</svg>

8
src/components/context/audio-conext.tsx

@ -38,7 +38,7 @@ interface AudioContextType {
audioRef: React.RefObject<HTMLAudioElement>; audioRef: React.RefObject<HTMLAudioElement>;
audio: Audio | null; // Store a single audio object audio: Audio | null; // Store a single audio object
getAudio: (id: number) => Promise<void>; getAudio: (id: number) => Promise<void>;
selectedReciter?: string;
selectedReciter?: {};
setSelectedReciter: React.Dispatch<React.SetStateAction<string | undefined>>; setSelectedReciter: React.Dispatch<React.SetStateAction<string | undefined>>;
} }
@ -73,7 +73,7 @@ export const AudioProvider: React.FC<{ children: ReactNode }> = ({
if (selectedReciter) { if (selectedReciter) {
selectedAudio = audioResponse.data.results.find( selectedAudio = audioResponse.data.results.find(
(audio) => audio.reciter?.id === selectedReciter
(audio) => audio.reciter?.id === selectedReciter.id
); );
} }
@ -97,7 +97,7 @@ export const AudioProvider: React.FC<{ children: ReactNode }> = ({
// If a reciter is selected, find the corresponding audio // If a reciter is selected, find the corresponding audio
if (selectedReciter) { if (selectedReciter) {
selectedAudio = audios.find( selectedAudio = audios.find(
(audio) => audio.reciter?.id === selectedReciter
(audio) => audio.reciter?.id === selectedReciter.id
); );
} }
@ -109,7 +109,7 @@ export const AudioProvider: React.FC<{ children: ReactNode }> = ({
// If no reciter is selected, update it with the first audio's reciter ID // If no reciter is selected, update it with the first audio's reciter ID
if (!selectedReciter && audios[0]?.reciter?.id) { if (!selectedReciter && audios[0]?.reciter?.id) {
setSelectedReciter(audios[0].reciter.id);
setSelectedReciter(audios[0].reciter);
} }
}, [selectedReciter, audios]); }, [selectedReciter, audios]);

12
src/components/context/ui.context.tsx

@ -31,6 +31,8 @@ type Action =
| { type: "CLOSE_SETTING" } | { type: "CLOSE_SETTING" }
| { type: "OPEN_RECITERS" } | { type: "OPEN_RECITERS" }
| { type: "CLOSE_RECITERS" } | { type: "CLOSE_RECITERS" }
| { type: "OPEN_AUDIO_SETTING" }
| { type: "CLOSE_AUDIO_SETTING" }
| { type: "CLOSE_DOWNLOAD" } | { type: "CLOSE_DOWNLOAD" }
| { type: "OPEN_AUDIO"; data: [] } | { type: "OPEN_AUDIO"; data: [] }
| { type: "CLOSE_AUDIO" } | { type: "CLOSE_AUDIO" }
@ -54,6 +56,10 @@ function uiReducer(state: typeof initialState, action: Action) {
return { ...state, displayReciters: true }; return { ...state, displayReciters: true };
case "CLOSE_RECITERS": case "CLOSE_RECITERS":
return { ...state, displayReciters: false }; return { ...state, displayReciters: false };
case "OPEN_AUDIO_SETTING":
return { ...state, displayAudioSetting: true };
case "CLOSE_AUDIO_SETTING":
return { ...state, displayAudioSetting: false };
case "CLOSE_DOWNLOAD": case "CLOSE_DOWNLOAD":
return { ...state, displayDownload: false }; return { ...state, displayDownload: false };
case "OPEN_AUDIO": case "OPEN_AUDIO":
@ -80,6 +86,8 @@ export const UIProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
const closeSetting = () => dispatch({ type: "CLOSE_SETTING" }); const closeSetting = () => dispatch({ type: "CLOSE_SETTING" });
const openReciters = () => dispatch({ type: "OPEN_RECITERS" }); const openReciters = () => dispatch({ type: "OPEN_RECITERS" });
const closeReciters = () => dispatch({ type: "CLOSE_RECITERS" }); const closeReciters = () => dispatch({ type: "CLOSE_RECITERS" });
const openAudioSetting = () => dispatch({ type: "OPEN_AUDIO_SETTING" });
const closeAudioSetting = () => dispatch({ type: "CLOSE_AUDIO_SETTING" });
const closeDownload = () => dispatch({ type: "CLOSE_DOWNLOAD" }); const closeDownload = () => dispatch({ type: "CLOSE_DOWNLOAD" });
const openAudio = (data: []) => { const openAudio = (data: []) => {
dispatch({ type: "OPEN_AUDIO", data }); dispatch({ type: "OPEN_AUDIO", data });
@ -105,7 +113,9 @@ export const UIProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
openAudio, openAudio,
closeAudio, closeAudio,
openReciters, openReciters,
closeReciters
closeReciters,
openAudioSetting,
closeAudioSetting
}; };
return <UIContext.Provider value={value}>{children}</UIContext.Provider>; return <UIContext.Provider value={value}>{children}</UIContext.Provider>;

127
src/components/language-switcher.tsx

@ -1,97 +1,134 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */ /* eslint-disable @typescript-eslint/ban-ts-comment */
"use client"
"use client";
import React, { useState, useEffect, useRef } from 'react';
import React, { useState, useEffect, useRef } from "react";
//@ts-ignore //@ts-ignore
import { IoGlobeSharp } from "react-icons/io5"; import { IoGlobeSharp } from "react-icons/io5";
import { IoIosArrowDown } from "react-icons/io"; import { IoIosArrowDown } from "react-icons/io";
import useLocalStorage from './utils/hooks/local-storage';
// import http from '@/api/http';
import { useUI } from "./context/ui.context";
import http from "@/api/http";
const LanguageSwitcher: React.FC = () => { const LanguageSwitcher: React.FC = () => {
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const { openModal } = useUI();
// Define `selectedLanguage` as a string literal type to match the keys of `languageOptions`
const [selectedLanguage, setSelectedLanguage] = useLocalStorage<"en" | "fa" | "fr" | "es" | "de" | "zh">("locale", "en");
const wrapperRef = useRef<HTMLDivElement | null>(null);
const [isClient, setIsClient] = useState(false); // State to track if we're on the client
const wrapperRef = useRef<HTMLDivElement | null>(null); // Ref for the dropdown container
const languageOptions = {
en: "English",
fa: "Persian",
fr: "French",
es: "Spanish",
de: "German",
zh: "Chinese",
};
const [windowWidth, setWindowWidth] = useState(
typeof window !== "undefined" ? window.innerWidth : 0
);
const selectedLanguageName = languageOptions[selectedLanguage] || "English";
const [languages, setLanguages] = useState<{ code: string; name: string }[]>(
[]
);
const [selectedLanguage, setSelectedLanguage] = useState<string>("en");
const [isLoading, setIsLoading] = useState<boolean>(false);
const [error, setError] = useState<string | null>(null);
const languages = Object.keys(languageOptions) as Array<"en" | "fa" | "fr" | "es" | "de" | "zh">;
const selectedLanguageName =
languages.find((lang) => lang.code === selectedLanguage)?.name || "English";
const toggleDropdown = () => { const toggleDropdown = () => {
setIsOpen(prevState => !prevState);
setIsOpen((prevState) => !prevState);
}; };
const selectLanguage = (lang: "en" | "fa" | "fr" | "es" | "de" | "zh") => {
const selectLanguage = (lang: string) => {
setSelectedLanguage(lang); setSelectedLanguage(lang);
localStorage.setItem("locale", lang); // Save the selected language in localStorage
setIsOpen(false); setIsOpen(false);
}; };
useEffect(() => { useEffect(() => {
// http.get("/v1/languages/").then((res)=>{
// console.log(res);
const fetchLanguages = async () => {
try {
setIsLoading(true);
const response = await http.get("v1/languages/");
setLanguages(response.data);
} catch (err) {
setError("Failed to load languages. Please try again.");
} finally {
setIsLoading(false);
}
};
// })
fetchLanguages();
const savedLanguage = localStorage.getItem("locale");
if (savedLanguage) {
setSelectedLanguage(savedLanguage);
}
}, []);
useEffect(() => {
const handleResize = () => setWindowWidth(window.innerWidth);
const handleClickOutside = (event: MouseEvent) => { const handleClickOutside = (event: MouseEvent) => {
if (wrapperRef.current && !wrapperRef.current.contains(event.target as Node)) {
if (
wrapperRef.current &&
!wrapperRef.current.contains(event.target as Node)
) {
setIsOpen(false); setIsOpen(false);
} }
}; };
document.addEventListener('mousedown', handleClickOutside);
window.addEventListener("resize", handleResize);
document.addEventListener("mousedown", handleClickOutside);
return () => { return () => {
document.removeEventListener('mousedown', handleClickOutside);
window.removeEventListener("resize", handleResize);
document.removeEventListener("mousedown", handleClickOutside);
}; };
}, []); }, []);
useEffect(() => {
setIsClient(true);
}, []);
if (!isClient) {
return null;
}
return ( return (
<div className="relative inline-block font-sans" ref={wrapperRef}> <div className="relative inline-block font-sans" ref={wrapperRef}>
<button <button
onClick={toggleDropdown}
onClick={() =>
windowWidth < 1024 ? openModal("LANGUAGES_VIEW") : toggleDropdown()
}
className="flex items-center px-3 py-2 h-11 border-gray-300 rounded-2xl bg-[#EBEBEB] text-black hover:bg-gray-50 focus:outline-none text-sm font-semibold" className="flex items-center px-3 py-2 h-11 border-gray-300 rounded-2xl bg-[#EBEBEB] text-black hover:bg-gray-50 focus:outline-none text-sm font-semibold"
aria-expanded={isOpen}
aria-controls="language-dropdown"
> >
<IoGlobeSharp color="black" size={25} className="mr-2" /> <IoGlobeSharp color="black" size={25} className="mr-2" />
{selectedLanguageName} {selectedLanguageName}
<span <span
className={`ml-2 transform transition-transform duration-200 ${isOpen ? 'rotate-180' : 'rotate-0'}`}
className={`ml-2 transform transition-transform duration-200 ${
isOpen ? "rotate-180" : "rotate-0"
}`}
> >
<IoIosArrowDown color="black" /> <IoIosArrowDown color="black" />
</span> </span>
</button> </button>
{isOpen && (
<ul className="absolute left-0 mt-1 w-full bg-white border border-gray-200 rounded shadow-lg z-50">
{languages.map(lang => (
{isOpen && !isLoading && !error && (
<ul
id="language-dropdown"
className="absolute left-0 mt-1 w-full bg-white border border-gray-200 rounded-2xl shadow-lg z-50 h-44 overflow-auto"
role="menu"
>
{languages.map((lang) => (
<li <li
key={lang}
className="px-4 py-2 hover:bg-gray-100 cursor-pointer"
onClick={() => selectLanguage(lang)}
key={lang.code}
className="px-4 py-2 hover:bg-gray-100 text-xs font-semibold cursor-pointer"
onClick={() => selectLanguage(lang.code)}
role="menuitem"
> >
{languageOptions[lang]}
{lang.name}
</li> </li>
))} ))}
</ul> </ul>
)} )}
{isLoading && (
<div className="absolute left-0 mt-1 w-full bg-white border border-gray-200 rounded shadow-lg z-50 p-4 text-center">
Loading...
</div>
)}
{error && (
<div className="absolute left-0 mt-1 w-full bg-white border border-gray-200 rounded shadow-lg z-50 p-4 text-red-500 text-center">
{error}
</div>
)}
</div> </div>
); );
}; };

2
src/components/layout/header.tsx

@ -12,7 +12,7 @@ const Header = () => {
displayDownload && "mt-[58px]" displayDownload && "mt-[58px]"
}`} }`}
> >
<div className="max-w-[1440px] h-20 m-auto flex justify-between w-full items-center">
<div className="max-w-[1440px] h-20 m-auto flex justify-between w-full items-center lg:p-6">
<div className="flex gap-11 h-full items-center"> <div className="flex gap-11 h-full items-center">
<Logo /> <Logo />
<div className="h-full"> <div className="h-full">

89
src/components/modals/audio-setting.tsx

@ -0,0 +1,89 @@
import React, { useEffect, useRef, useState } from "react";
import { useUI } from "../context/ui.context";
import { IoMdClose } from "react-icons/io";
import { IoIosArrowDown } from "react-icons/io";
import SpeedImg from "../../../public/assets/images/Group 852.svg";
import Image from "next/image";
import { useAudio } from "../context/audio-conext";
interface ModalProps {
className?: string; // Optional className with a default value
}
const AudioSetting: React.FC<ModalProps> = ({ className = "" }) => {
const { displaySetting, closeModal, openModal } = useUI();
const { selectedReciter, audioRef } = useAudio() as {
selectedReciter: { name: string } | null;
audioRef: React.RefObject<HTMLAudioElement>;
};
const modalRef = useRef<HTMLDivElement>(null);
const [windowWidth, setWindowWidth] = useState(typeof window !== "undefined" ? window.innerWidth : 0);
const [speed, setSpeed] = useState(1);
useEffect(() => {
const handleResize = () => setWindowWidth(window.innerWidth);
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
}, []);
useEffect(() => {
if (audioRef?.current) {
audioRef.current.playbackRate = speed; // Update the audio playback speed
}
}, [speed]); // Re-run this effect whenever speed changes
const modalClasses = windowWidth < 1024 ? "" : "max-w-96 bottom-20 right-0";
const increaseSpeed = () => {
setSpeed((prevSpeed) => (prevSpeed < 2 ? +(prevSpeed + 0.2).toFixed(1) : 1));
};
return (
<div
ref={modalRef}
className={`flex flex-col z-50 bg-white text-black min-h-60 max-h-[739px] rounded-3xl absolute gap-6 p-6 w-full bottom-0 ${modalClasses} ${className}`}
role="dialog"
aria-modal="true"
>
<div className="flex justify-between items-center mb-5">
<div className="text-[#8B8B8B] text-sm font-bold">Audio Settings</div>
<button onClick={closeModal} aria-label="Close settings modal">
<IoMdClose size={18} />
</button>
</div>
<div className="flex flex-col gap-2">
<div className="text-[#8B8B8B] text-xs font-bold">Reciters</div>
<button
onClick={() => openModal("RECITERS_VIEW")}
className="p-3 border rounded-2xl flex justify-between items-center w-full"
aria-label="Select a reciter"
>
<p>{selectedReciter?.name || "Select Reciter"}</p>
<IoIosArrowDown />
</button>
</div>
<button
onClick={increaseSpeed}
className="bg-[#F4846F] flex gap-5 items-center p-4 rounded-2xl max-w-max"
aria-label="Increase speed"
>
<div className="flex gap-2 items-center">
<Image src={SpeedImg} alt="Speed" width={20} height={20} />
<p className="text-white text-sm font-semibold">Speed</p>
</div>
<div className="bg-[#EB6E57] rounded-xl p-2">
<p className="text-white text-base font-semibold">{speed} x</p>
</div>
</button>
</div>
);
};
export default AudioSetting;

92
src/components/modals/languages-modal.tsx

@ -0,0 +1,92 @@
import http from "@/api/http";
import { useEffect, useState } from "react";
import { IoMdClose } from "react-icons/io";
import { useUI } from "../context/ui.context";
import { FaCheckCircle } from "react-icons/fa";
interface Language {
code: string; // Assuming each language has a unique ID
name: string;
}
const LanguageModal = () => {
const [languages, setLanguages] = useState<Language[]>([]);
const [selectedLanguage, setSelectedLanguage] = useState<string>("");
const [isLoading, setIsLoading] = useState<boolean>(true);
const [error, setError] = useState<string | null>(null);
const { closeModal } = useUI();
useEffect(() => {
const fetchLanguages = async () => {
try {
setIsLoading(true);
const response = await http.get("v1/languages/");
setLanguages(response.data);
} catch (err) {
setError("Failed to load languages. Please try again.");
} finally {
setIsLoading(false);
}
};
fetchLanguages();
const locale = localStorage.getItem("locale");
if (locale) {
setSelectedLanguage(locale);
}
}, []);
const handleLanguageSelect = (code: string) => {
setSelectedLanguage(code);
localStorage.setItem("locale", code); // Persist selection in localStorage
};
if (isLoading) {
return <div>Loading languages...</div>;
}
if (error) {
return <div className="text-red-500">{error}</div>;
}
return (
<div className="p-4">
<div className="flex justify-between items-center mb-4">
<h2 className="text-white font-bold">Choose Language</h2>
<button
onClick={closeModal}
aria-label="Close language modal"
className="text-white"
>
<IoMdClose size={18} />
</button>
</div>
{languages.length > 0 ? (
<ul>
{languages.map((item) => (
// eslint-disable-next-line jsx-a11y/role-supports-aria-props
<li
key={item.code}
onClick={() => handleLanguageSelect(item.code)}
className={`p-4 my-4 rounded-2xl cursor-pointer flex items-center gap-4 ${
selectedLanguage === item.code
? "bg-white border-2 border-[#F4846F]"
: "bg-white"
}`}
aria-selected={selectedLanguage === item.code}
>
{selectedLanguage === item.code && <FaCheckCircle color="#F4846F" />}
<p>{item.name}</p>
</li>
))}
</ul>
) : (
<p>No languages available.</p>
)}
</div>
);
};
export default LanguageModal;

4
src/components/modals/modal-manager.tsx

@ -2,6 +2,8 @@ import { useUI } from "../context/ui.context";
import Modal from "./modal"; import Modal from "./modal";
import dynamic from "next/dynamic"; import dynamic from "next/dynamic";
import RecitersModal from "./reciters"; import RecitersModal from "./reciters";
import AudioSetting from "./audio-setting";
import LanguageModal from "./languages-modal";
// import Newsletter from "../newsletter"; // import Newsletter from "../newsletter";
const SettingModal = dynamic(() => import("@/components/modals/setting")); const SettingModal = dynamic(() => import("@/components/modals/setting"));
const SearchModal = dynamic(() => import("@/components/modals/search-modal")); const SearchModal = dynamic(() => import("@/components/modals/search-modal"));
@ -21,6 +23,8 @@ const ManagedModal: React.FC = () => {
{modalView === "SETTING_VIEW" && <SettingModal />} {modalView === "SETTING_VIEW" && <SettingModal />}
{modalView === "RECITERS_VIEW" && <RecitersModal />} {modalView === "RECITERS_VIEW" && <RecitersModal />}
{modalView === "SEARCH_VIEW" && <SearchModal />} {modalView === "SEARCH_VIEW" && <SearchModal />}
{modalView === "AUDIO_SETTING_VIEW" && <AudioSetting/>}
{modalView === "LANGUAGES_VIEW" && <LanguageModal/>}
{/* {modalView === "SIGN_UP_VIEW" && <SignUpForm />} {/* {modalView === "SIGN_UP_VIEW" && <SignUpForm />}
{modalView === "FORGET_PASSWORD" && <ForgetPasswordForm />} {modalView === "FORGET_PASSWORD" && <ForgetPasswordForm />}
{modalView === "PRODUCT_VIEW" && <ProductPopup />} {modalView === "PRODUCT_VIEW" && <ProductPopup />}

40
src/components/modals/reciters.tsx

@ -12,8 +12,8 @@ interface ModalProps {
} }
const RecitersModal: React.FC<ModalProps> = ({ className = "" }) => { const RecitersModal: React.FC<ModalProps> = ({ className = "" }) => {
const { displaySetting, modalView, closeModal } = useUI();
const {selectedReciter , setSelectedReciter} = useAudio()
const { displaySetting, closeModal } = useUI();
const { selectedReciter, setSelectedReciter } = useAudio();
const modalRef = useRef<HTMLDivElement>(null); const modalRef = useRef<HTMLDivElement>(null);
const closeButtonRef = useRef<HTMLButtonElement>(null); const closeButtonRef = useRef<HTMLButtonElement>(null);
const previouslyFocusedElement = useRef<HTMLElement | null>(null); const previouslyFocusedElement = useRef<HTMLElement | null>(null);
@ -22,7 +22,7 @@ const RecitersModal: React.FC<ModalProps> = ({ className = "" }) => {
const params = useParams(); const params = useParams();
const slug = params?.slug as string; const slug = params?.slug as string;
const id = slug.split("-").pop(); const id = slug.split("-").pop();
const modalClasses = windowWidth < 1024 ? "absolute w-full bottom-0" : "";
const modalClasses = windowWidth < 1024 ? "" : "max-w-96 bottom-20 right-0";
console.log(windowWidth); console.log(windowWidth);
@ -83,14 +83,13 @@ const RecitersModal: React.FC<ModalProps> = ({ className = "" }) => {
}; };
}, []); }, []);
console.log(reciters); console.log(reciters);
reciters.map((item)=>{
reciters.map((item) => {
console.log(item); console.log(item);
})
});
return ( return (
<div <div
ref={modalRef} ref={modalRef}
className={`flex flex-col z-50 bg-white text-black min-h-60 max-h-[739px] rounded-3xl ${modalClasses} ${className}`}
className={`flex flex-col z-50 bg-white text-black min-h-60 max-h-[739px] rounded-3xl absolute w-full bottom-0 ${modalClasses} ${className}`}
role="dialog" role="dialog"
aria-modal="true" aria-modal="true"
> >
@ -103,12 +102,27 @@ const RecitersModal: React.FC<ModalProps> = ({ className = "" }) => {
</div> </div>
<div> <div>
{reciters.map((reciter) => ( {reciters.map((reciter) => (
<div onClick={(()=>setSelectedReciter(reciter.id))} key={reciter?.id} className="flex py-4 px-6 items-center gap-4 border-b hover:bg-[#EBEBEB] cursor-pointer">
<div className={`w-4 h-4 rounded-full border border-black ${selectedReciter === reciter?.id && "bg-[#F4846F] border-0"}`}></div>
<Image className="rounded-full w-12 h-12" alt={reciter?.name} width={50} height={50} src={reciter?.avatar?.sm}/>
<p>
{reciter?.name}
</p>
<div
onClick={() => {
setSelectedReciter(reciter);
closeModal();
}}
key={reciter?.id}
className="flex py-4 px-6 items-center gap-4 border-b hover:bg-[#EBEBEB] cursor-pointer"
>
<div
className={`w-4 h-4 rounded-full border border-black ${
selectedReciter === reciter?.id && "bg-[#F4846F] border-0"
}`}
></div>
<Image
className="rounded-full w-12 h-12"
alt={reciter?.name}
width={50}
height={50}
src={reciter?.avatar?.sm}
/>
<p>{reciter?.name}</p>
</div> </div>
))} ))}
</div> </div>

28
src/components/sticky-components/audio-controls.tsx

@ -7,6 +7,7 @@ import Image from "next/image";
import { useUI } from "../context/ui.context"; import { useUI } from "../context/ui.context";
import { useAudio } from "../context/audio-conext"; import { useAudio } from "../context/audio-conext";
import { IoPersonSharp } from "react-icons/io5"; import { IoPersonSharp } from "react-icons/io5";
import { useParams } from "next/navigation";
const AudioControls = () => { const AudioControls = () => {
const { openModal, closeAudio } = useUI(); const { openModal, closeAudio } = useUI();
@ -14,6 +15,8 @@ const AudioControls = () => {
const [isPlaying, setIsPlaying] = useState(false); const [isPlaying, setIsPlaying] = useState(false);
const [currentTime, setCurrentTime] = useState(0); const [currentTime, setCurrentTime] = useState(0);
const [duration, setDuration] = useState(1); // Default duration to 1 to prevent division by zero const [duration, setDuration] = useState(1); // Default duration to 1 to prevent division by zero
const params = useParams();
const slug = params?.slug as string;
const play = () => { const play = () => {
if (audioRef.current && audio) { if (audioRef.current && audio) {
@ -67,7 +70,26 @@ const AudioControls = () => {
const newNumerator = (numerator * 200) / denominator; const newNumerator = (numerator * 200) / denominator;
return newNumerator; // Adjust to start from -20 degrees return newNumerator; // Adjust to start from -20 degrees
} }
function processSlug(slug: string): string {
if (!slug) return "";
// Split the slug by "-"
const parts = slug.split("-");
// Remove the last word
if (parts.length === 0) return "";
parts.pop();
// Convert each word to PascalCase
const pascalCaseWords = parts.map(
(word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()
);
// Join the words with spaces
const result = pascalCaseWords.join(" ");
return result;
}
useEffect(() => { useEffect(() => {
if (audioRef.current) { if (audioRef.current) {
audioRef.current.addEventListener("timeupdate", onTimeUpdate); audioRef.current.addEventListener("timeupdate", onTimeUpdate);
@ -104,7 +126,7 @@ const AudioControls = () => {
<div className="text-[#F4846F] text-3xl font-bold">.</div> <div className="text-[#F4846F] text-3xl font-bold">.</div>
</div> </div>
<button <button
className="p-4 rounded-full w-12 h-12 flex items-center justify-center bg-[#F4846F]"
className="p-4 rounded-full w-12 h-12 flex items-center justify-center z-10 bg-[#F4846F]"
onClick={() => { onClick={() => {
isPlaying ? pause() : play(); isPlaying ? pause() : play();
}} }}
@ -114,7 +136,7 @@ const AudioControls = () => {
</div> </div>
</div> </div>
<div> <div>
<p>{audio?.reciter?.name}</p>
<p>{processSlug(slug)}</p>
<div className="flex items-center"> <div className="flex items-center">
{audio?.reciter?.avatar?.sm ? ( {audio?.reciter?.avatar?.sm ? (
<Image <Image
@ -132,7 +154,7 @@ const AudioControls = () => {
</div> </div>
</div> </div>
<div className="flex gap-4 items-center"> <div className="flex gap-4 items-center">
<button onClick={() => openModal("RECITERS_VIEW")}>
<button onClick={() => openModal("AUDIO_SETTING_VIEW")}>
<Image src={Setting} alt="Setting" /> <Image src={Setting} alt="Setting" />
</button> </button>
<button <button

48
src/components/utils/hooks/local-storage.tsx

@ -1,48 +0,0 @@
import { useState, useEffect } from "react";
/**
* useLocalStorage Hook
*
* @param key - The key in localStorage
* @param initialValue - The initial value to use if key is not found
* @returns [storedValue, setValue] - The current value and a setter function
*/
function useLocalStorage<T>(key: string, initialValue: T): [T, (value: T | ((val: T) => T)) => void] {
const [storedValue, setStoredValue] = useState<T>(() => {
try {
const item = window.localStorage.getItem(key);
return item ? (JSON.parse(item) as T) : initialValue;
} catch (error) {
console.error("Error reading localStorage key “" + key + "”:", error);
return initialValue;
}
});
const setValue = (value: T | ((val: T) => T)) => {
try {
const valueToStore = value instanceof Function ? value(storedValue) : value;
setStoredValue(valueToStore);
window.localStorage.setItem(key, JSON.stringify(valueToStore));
} catch (error) {
console.error("Error setting localStorage key “" + key + "”:", error);
}
};
useEffect(() => {
const handleStorageChange = () => {
try {
const item = window.localStorage.getItem(key);
setStoredValue(item ? (JSON.parse(item) as T) : initialValue);
} catch (error) {
console.error("Error reading localStorage key “" + key + "”:", error);
}
};
window.addEventListener("storage", handleStorageChange);
return () => window.removeEventListener("storage", handleStorageChange);
}, [key, initialValue]);
return [storedValue, setValue];
}
export default useLocalStorage;
Loading…
Cancel
Save