Chain NextAuth and Internationalization Middlewares in NextJs 14

Поділитися
Вставка
  • Опубліковано 26 сер 2024

КОМЕНТАРІ • 128

  • @alexavery8182
    @alexavery8182 9 місяців тому +7

    Hamed single-handedly taught me Nextjs and not just on a surface level, he taught me from the ground up, so not only can I work with Nextjs but I actually have a true understanding of *how* its working and why it works the way it does
    I can't thank him enough for all that he has taught me

    • @hamedbahram
      @hamedbahram  9 місяців тому +1

      My pleasure! I'm so glad to hear that, Alex! Appreciate your comment man.

  • @Couchwurst
    @Couchwurst 4 дні тому +1

    Hey Hamed!
    I just recently stumbled upon your great content! I love how clear, concise and smooth your explanations are, as well as the effort that seems to go into each of your videos. Thank you for this!
    And especially this kind of video, that goes just a bit beyond the standard documentation, is really valuable. Sometimes it is not to hard to figure something functional out, but keeping in mind scalable best practices is a different story. Very appreciated! :)

    • @hamedbahram
      @hamedbahram  4 дні тому

      Welcome aboard! I'm glad you're finding the content useful and thanks for the kind words, I appreciate it!

  • @skoshk1950
    @skoshk1950 2 місяці тому +1

    My god. All your videos are exactly what I need. I rarely comment and you made me do it. I was trying to implement internationalization in my app and after watching your chain and internalization videos I've been struggling to get them to work together with next auth. And bam, I come across this video today. Nice!

    • @hamedbahram
      @hamedbahram  2 місяці тому

      Glad to hear that! Thanks for leaving a comment

  • @alexeysmirnov9288
    @alexeysmirnov9288 5 місяців тому +1

    This is not the first time I want to thank you Hamed! And it looks like not the last one )

    • @hamedbahram
      @hamedbahram  5 місяців тому

      My pleasure! I appreciate that.

  • @jannes97
    @jannes97 6 місяців тому +1

    First of all, thank you very much for this video. Videos like this are perfect for someone who wants to go deeper into programming. Why are there so few videos about internationalization? I mean about the entire topic, which library and benefits i18next, next-18n, paraglideJS from inlang, and so on. This list feels endless.

    • @hamedbahram
      @hamedbahram  6 місяців тому

      My pleasure! I'm glad to hear that. You're right the topic is widely used but not so much content around it.

  • @Przekrent4
    @Przekrent4 5 місяців тому +1

    What about "error.tsx" files? "not-found.tsx" page is a server component, so you can easly import the translations using `getDictionary()`. But "error.ts" has to be a client component and you cant pass props to it. So it look that creating a context for client-side translations is necessary.

    • @hamedbahram
      @hamedbahram  5 місяців тому

      That's right. I'd have to include that in future videos.

  • @j_pestana
    @j_pestana 9 місяців тому +1

    I've been looking for some clues on this exact usecase, without any luck, for quite some time.
    Thanks man!!

    • @hamedbahram
      @hamedbahram  9 місяців тому

      There you have it man! I'm glad to hear that.

  • @caceresjuan
    @caceresjuan 6 місяців тому

    you have no idea Hamed how much I love you

  • @mihaimaxim4052
    @mihaimaxim4052 9 місяців тому +1

    The amount of boilerplate is just insane

    • @mihaimaxim4052
      @mihaimaxim4052 9 місяців тому +1

      You can't have different middlewares that match different routes? How can anyone take this framework seriously 😅!?

    • @hamedbahram
      @hamedbahram  9 місяців тому

      Thanks for the feedback.

  • @saddamhussainEscap
    @saddamhussainEscap 5 місяців тому +1

    thank you hamed for all efforts you makes and valuable information we get from you but can we do this with next-auth v5

    • @hamedbahram
      @hamedbahram  5 місяців тому +1

      Absolutely! I'll have videos on the v5 in near future.

  • @zlatkoiliev8927
    @zlatkoiliev8927 2 місяці тому +1

    In this way we loose some of the functionalities of next-intl, for example it exposes one hook for client components - useTranslations() and this is no longer working with your example.

    • @hamedbahram
      @hamedbahram  2 місяці тому

      That’s right, this is a simple implementation without depending on a third party library.

    • @zlatkoiliev8927
      @zlatkoiliev8927 2 місяці тому

      @@hamedbahram and what if we depend on next-auth v5 and next-intl?!?

  • @jakubjankowski9391
    @jakubjankowski9391 2 місяці тому +1

    Similar idea to chain middleware, less code:
    function chainMiddleware(...functions: MiddlewareFactory[]): CustomMiddleware {
    return functions.reduceRight(
    (next, fn) => fn(next),
    (request, event, response) => response
    )
    }
    export const middleware = chainMiddleware(redirectToLocaleMiddleware, setCurrentPathMiddleware)

    • @hamedbahram
      @hamedbahram  2 місяці тому

      Nice! I like the reduceRight thing.

  • @0xtz_
    @0xtz_ 9 місяців тому +1

    👏 amazing video as always, keep going 💪

  • @Foused87
    @Foused87 6 місяців тому +1

    I have admin page with sub pages e.g /admin/settings. How can I protect all these sub routes instead of writing them manuall for example.: const protectedPaths = ['/admin', '/admin/settings', '/admin/forms']?

    • @hamedbahram
      @hamedbahram  6 місяців тому

      You can use regex to accomplish that [ '/admin/:path*' ]

    • @Foused87
      @Foused87 6 місяців тому

      @@hamedbahram thanks 👍

  • @HigherStudyAspirant
    @HigherStudyAspirant 5 місяців тому +1

    can we have different matcher for different middlewares. Suppose for authentication, some pages in matcher while for internationlization we want all pages in matcher.

    • @hamedbahram
      @hamedbahram  5 місяців тому +1

      At this point we can only have one config for the middleware. You need to use conditional statements to check the `pathname` inside each middleware to decide what needs to happen.

  • @fakhrulislamfuad8072
    @fakhrulislamfuad8072 9 місяців тому +1

    Awesome video bro.. but you've told that you'll bring something about localizing the not-found page bro.. Hope that comes soon☺

    • @hamedbahram
      @hamedbahram  9 місяців тому

      Akh I forgot that, I'll add it to this project...

    • @fakhrulislamfuad8072
      @fakhrulislamfuad8072 9 місяців тому +1

      @@hamedbahram thanks a lot bro.. Keep this good work up🖤

    • @fakhrulislamfuad8072
      @fakhrulislamfuad8072 9 місяців тому

      @@hamedbahram still the project isn't updated bro.. eagerly waiting🙂

  • @kakun7238
    @kakun7238 6 місяців тому +1

    Argument of type '{ req: NextRequest; }' is not assignable to parameter of type 'GetTokenParams'.
    Type '{ req: NextRequest; }' is missing the following properties from type 'GetTokenParams': salt, secret
    using next-auth: 5.0.0-beta.5

    • @hamedbahram
      @hamedbahram  6 місяців тому +1

      This is using next-auth v4. Check out the source code.

  • @fasilbabu4967
    @fasilbabu4967 6 місяців тому +1

    How to integrate next-intl with contentlayer ?

    • @hamedbahram
      @hamedbahram  6 місяців тому

      Good question, I've to yet work on a video on contentlayer.

  • @Foused87
    @Foused87 6 місяців тому +1

    How about removing default locale from the url and redirecting to not found page? Is it solved now?

    • @hamedbahram
      @hamedbahram  6 місяців тому

      Essentially the issue is that automatic redirects to not-found do not work inside of a dynamic segment like [lang]. So you need to use a catch-all that calls notFound().
      You will need:
      1. Catch all component: [...not_found]/page.tsx
      2. Not found page: not-found.tsx
      ```
      // [...not_found]/page.tsx
      import { notFound } from 'next/navigation';
      export default function NotFoundCatchAll() {
      notFound();
      }
      ```
      Another problem is that inside of your not-found page you will not have access to page params. So you cannot access the locale from page params. You can instead set a custom header in your middleware if you want to localize this not-found page.
      ```
      // not-found.tsx
      import { headers } from 'next/headers'
      export default async function NotFound() {
      const headersList = headers()
      const locale = headersList.get('x-i18n-locale') || 'en'
      ...
      }
      ```

    • @Foused87
      @Foused87 6 місяців тому

      @@hamedbahram thanks for the answer

  • @d4rzk252
    @d4rzk252 9 місяців тому +1

    Hello, I just wanted to ask. When building (pushing to vercel) this project it throws an error and fails… Do you know how to fix it?

    • @d4rzk252
      @d4rzk252 9 місяців тому

      something like this:
      Type error: Route app/api/auth/[...nextauth]/route.ts does not match the required types of a Next.js Route. authOptions is not a valid Route export field.

    • @d4rzk252
      @d4rzk252 9 місяців тому

      when you remove “export”:
      Attempted import error: 'authOptions' is not exported from '@/app/api/auth/[...nextauth]/route' (imported as 'authOptions').

    • @hamedbahram
      @hamedbahram  9 місяців тому

      You can extract the next-auth options out of the route handler and export it from a sibling file like `_options.ts` in the auth folder. I've updated the code to reflect this.

    • @d4rzk252
      @d4rzk252 9 місяців тому +1

      @@hamedbahram thank you very much

    • @hamedbahram
      @hamedbahram  9 місяців тому

      @@d4rzk252 Anytime!

  • @KuldeepYadav-vh5hu
    @KuldeepYadav-vh5hu 7 місяців тому +1

    Can you make a video on how we can use custom auth middleware (not next-auth) with next-intl localization.

    • @hamedbahram
      @hamedbahram  7 місяців тому

      Hmm 🤔 thanks for the suggestion! I'll have that in mind.

  • @arazmmmdli6126
    @arazmmmdli6126 9 місяців тому +1

    We created a lang inside the application folder and did everything inside it, but how do we use the not found file in this situation? How can I make the contents of the not found file to be included in the layout inside the lang folder?

    • @hamedbahram
      @hamedbahram  9 місяців тому +1

      That's a good question! I haven't found a way to implement the `not-found` page in this folder structure. I will continue digging into this...

    • @arazmmmdli6126
      @arazmmmdli6126 9 місяців тому

      Thanks, if you find a way, I would be very happy if you share it on your channel because I am looking for a way too.@@hamedbahram

  • @user-dy8vh6uu2y
    @user-dy8vh6uu2y 9 місяців тому

    Thank you very much! It's a priceless lesson 💎

    • @hamedbahram
      @hamedbahram  9 місяців тому +1

      My pleasure! Glad to hear that.

  • @erikkhachatryan32
    @erikkhachatryan32 7 місяців тому +2

    It would be great if you didn’t pass lang={params.lang} with props every time, can you do something for this?

    • @hamedbahram
      @hamedbahram  7 місяців тому

      Hmm 🤔 you can share it via global state provider.

    • @erikkhachatryan32
      @erikkhachatryan32 7 місяців тому

      the translation to next-intl is very excellent and simple, but there is a problem when you want to work on both next-intl and next-auth at the same time, if there are solutions, please make a video @@hamedbahram

  • @Quick_Code
    @Quick_Code 9 місяців тому +1

    when your redirecting the user to a new page if it is not authenticated then internationalization will not apply? right

    • @hamedbahram
      @hamedbahram  9 місяців тому

      Good question! If you need your auth pages to be translated you can run the i18n middleware first.

  • @in43sh
    @in43sh 9 місяців тому +1

    Thank you for the video! Could you explain again the moment on 13:00 why we're not turning the whole app into client component/app?

    • @hamedbahram
      @hamedbahram  9 місяців тому

      Watch this video where I dive deeper into server and client components → ua-cam.com/video/3Dw6D_WuzSE/v-deo.html

  • @Foused87
    @Foused87 6 місяців тому +1

    I want admin to have an access to view and edit dictionaries files in admin panel (/admin/settings). Changes won't be frequent. Where do you recommend to store these dictionaries? Database? Locally? As a string/json?

    • @hamedbahram
      @hamedbahram  6 місяців тому

      I would use a database for that. perhaps a cached, fast, key-value db like redis or Vercel KV

  • @amelianceskymusic
    @amelianceskymusic 9 місяців тому +1

    Is it possible to somehow implement a fallback to the default language for keys that are not written in json for other languages?

    • @hamedbahram
      @hamedbahram  9 місяців тому

      With the current implementation if the user prefers a language that you don't support it will use the default locale in this case `en`. Does this answer your question?

    • @amelianceskymusic
      @amelianceskymusic 9 місяців тому

      ​@@hamedbahram I mean, if I have 2 files, for example en.json
      {
      "buttons": {
      "signin": "Sign In",
      "signinWithGoogle": "Sign In with Google"
      }
      }
      and uk.json
      {
      "buttons": {
      "signinWithGoogle": "Увійти через Google"
      }
      }
      If my language is uk, I will not receive anything on the buttons.signin key and there will be no text on the button. Instead, it would be nice if there was some fallback and I got the string 'Sign In' from the default language
      I know that some implementations provide for this

  • @sealuke2724
    @sealuke2724 8 місяців тому +1

    it is helpful using next14 middleware with next-auth. i have a question! if i want to add role-based authorization after authentication, add middleware3 right?

    • @hamedbahram
      @hamedbahram  8 місяців тому +1

      It can also be implemented in the same auth middleware. Watch this video → ua-cam.com/video/urZ0iMugiiI/v-deo.html

  • @tobiasgleiter
    @tobiasgleiter 7 місяців тому +1

    How to handle dynamic routing to protect pages?

    • @hamedbahram
      @hamedbahram  7 місяців тому +1

      Can you expand on your question?

    • @tobiasgleiter
      @tobiasgleiter 7 місяців тому

      Thank you for your answer. I've implemented Internationalization and Authentication through middleware-chaining (your tutorial). How is it possible to protect paths like '/posts/[id]' e.g. with '/posts/:path*' .
      I will try to find a solution. @@hamedbahram

    • @tobiasgleiter
      @tobiasgleiter 7 місяців тому

      If I enter the route: /en/post/1234, the protectedPathsWithLocale.includes returns false (protected path: '/post')

    • @tobiasgleiter
      @tobiasgleiter 7 місяців тому +1

      I found a solution to solve this issue. The Issue was protectedPathWithLocale.inclues(pathname) which should be the way around: pathname.inclues().
      In the auth-middleware change the redirect to login if clause: if(!token && protectedPathsWithLocale.some((path) => pathname.includes(path))) { ... }
      This solution include all subfolders for a given protected path e.g. ('/protected' protects also the route '/protected/example' and therefore '/protected/[id]' is protected too)

    • @hamedbahram
      @hamedbahram  7 місяців тому +1

      @@tobiasgleiter I see. Thanks for sharing this. Can you make a PR on the repo?

  • @jothikannanchandramohan6660
    @jothikannanchandramohan6660 8 місяців тому +1

    The protected routes not working for the dynamic routes and catch all segments, do you have fix for that?

    • @hamedbahram
      @hamedbahram  8 місяців тому

      Why not? Getting any errors? Keep in mind that everything needs to live inside the `[lang]` folder.

    • @jothikannanchandramohan6660
      @jothikannanchandramohan6660 8 місяців тому

      @@hamedbahram no errors, Yes I have everything inside [lang] folder. After clearing the session, the protection only works for the added paths in the protectedPaths, if we try to access the dynamic paths or catch all segments, it still accessible.

    • @jothikannanchandramohan6660
      @jothikannanchandramohan6660 8 місяців тому +1

      I got fixed it by checking with startsWith
      const protectedPathsWithLocale = getProtectedRoutes(protectedPaths, [...i18n.locales])
      const isProtectedRoute = protectedPathsWithLocale.some((path) => pathname.startsWith(path))

    • @hamedbahram
      @hamedbahram  8 місяців тому

      @@jothikannanchandramohan6660 awesome!

    • @jothikannanchandramohan6660
      @jothikannanchandramohan6660 8 місяців тому

      @@hamedbahram But I need one help on how to prevent the user back to login screen when the user is logged in?

  • @eviltables7235
    @eviltables7235 9 місяців тому +1

    Really cool video. Kind of unfortunate that you need to repeat the type interface for the AuthButtonProps--would there be any way to somehow import the type there as a key from the dictionary itself? That way if you rename a key you wouldn't have to do it in two places

    • @hamedbahram
      @hamedbahram  9 місяців тому

      Thanks! Yeah that's a good idea and it'll be easier to type your dictionaries.

  • @AslanM94
    @AslanM94 9 місяців тому +1

    Thanks for video, but how can we implement access token and refresh token with SSR or SSG and also ISR ? Could you answer ?

    • @hamedbahram
      @hamedbahram  9 місяців тому +1

      That's a different topic all together, what service are you using for authentication?

    • @AslanM94
      @AslanM94 9 місяців тому

      @@hamedbahram Own via the Rest Api, But I would like to see an implementation with your approach, How would you implement such an opportunity with Access and Refresh tokens for SSR ISR.

  • @rayhanislam7518
    @rayhanislam7518 9 місяців тому

    I used your previous I18n tutorial where has a problem. If I have any searchQuearyParams in brower that will clear when I change the language.

    • @hamedbahram
      @hamedbahram  9 місяців тому

      Hmm 🤔 I'll look into that.

    • @d4rzk252
      @d4rzk252 9 місяців тому +1

      Im interested in this too.

    • @hamedbahram
      @hamedbahram  9 місяців тому

      @@d4rzk252 absolutely!

  • @Hajir2005
    @Hajir2005 9 місяців тому +1

    Thanks Hamed, you explained it very clearly! The only issue I have right now is that is's not allowing to have the a default path without /en. As you explained, it is not possible with this approach. But if you have another method that can work with default path to use english, and allow other languages to have /xx at the end it would be perfect.
    By the way, there's a lang="en" on the html syntax and it doesn't change when we change the language, would that affect SEO or anything else?
    How would you have an alias for a language? for example Chinese is /zh, but we want to show it as /cn. I could manage it by changing the locales from zh to cn and renaming the files and it worked. just wondering if it affects to find the locale based on the users' browser. Probably what I did is not correct.
    Thanks

    • @hamedbahram
      @hamedbahram  9 місяців тому +1

      My pleasure!
      It'll require a different implementation to get rid of the default locale. The `lang` attribute on the html should update to the correct local if you use the `params.locale` for the value. You can redirect the user to whatever alias you want for a specific language.

  • @karacan2469
    @karacan2469 5 місяців тому

    it seems when we change the language in your solution we actually reload the page, I am right?

    • @hamedbahram
      @hamedbahram  5 місяців тому

      We're using the link component in the `locale-switcher` to navigate to the desired locale.

  • @BardiaVar
    @BardiaVar 9 місяців тому +1

    Hi, hamed! I need your help. Honestly, I'm a bit confused about how to implement a rate limiter without using platforms like 'upstash.' Currently, I've tried the Node limiter, Express Rate Limit, and LRU Cache packages, but I don't know what the standard and scalable way to implement it is. I don't know how to make middleware that controls the rate limit.
    can you guide me

    • @hamedbahram
      @hamedbahram  9 місяців тому +1

      Hi, Bardia! I've covered a service called Zuplo that makes building API and rate limiting very easy. You can watch it here → ua-cam.com/video/zz6BG2AF-N4/v-deo.html Out of curiosity why would you want to implement rate limiting yourself rather than services like upstash?

    • @BardiaVar
      @BardiaVar 8 місяців тому +1

      @hamedbahram
      Thank you very much. How strange that I had not seen your rate-limiting video on the channel! Unfortunately, due to internet restrictions in Iran and unstable conditions, it is often difficult to connect to these platforms (Upstash).

    • @hamedbahram
      @hamedbahram  8 місяців тому

      @@BardiaVar I see. Well try zuplo and see if that works in your network. If that didn't work look into this package → github.com/mailgun/gubernator

    • @BardiaVar
      @BardiaVar 8 місяців тому +1

      thanks a lot. Unfortunately, zuplo didn't work. If possible, post a video on this topic as it is very little covered.@@hamedbahram

    • @hamedbahram
      @hamedbahram  8 місяців тому

      @@BardiaVar sorry to hear that. Sure I'll have that in mind.

  • @Desertsol1
    @Desertsol1 8 місяців тому +1

    Hi, Thanks for making your videos, they helps me alot. Sorry for asking here, but do you in any video show how we could send custom data from a client component to the [...nextauth] > route.js server component?
    Im in the process to need to add a remember me on the login page and need to set the expire date in the next-auth cookie to a custom date, if the remember me bool is true on the login client component.

    • @hamedbahram
      @hamedbahram  8 місяців тому +1

      You can pass additional parameters to the `/authorize` endpoint through the third argument of `signIn()` and then access it on the `req.body` in your `[...next-auth]` handler.

    • @Desertsol1
      @Desertsol1 8 місяців тому

      @@hamedbahram Thanks for reply, after testing with custom login credentials provider, with both javascript & typescript. The body.req is allways null at the top most level in the handler. In the next-auth functions that accepts req & credentials as a property, we can access the custom data set in the third parameter in the signIn() from the client.
      For example the authorize() and signIn() callback functions can access the custom data from the signIn() on the client-side.
      But if one wants configure the next-auth settings on other places outside of the callback functions with data coming from the client, session:{ } for example.
      This is not possible since req.body is allways null?

  • @rayhanislam7518
    @rayhanislam7518 9 місяців тому

    I have a external api for authetication, and this will be either number or email. so in nextAuth credential How can I manage it. Or If i use manually middleware how to handle it without nextAuth. If I use my manual middleware so how can I set cookie ans secure httpOnly?

    • @hamedbahram
      @hamedbahram  9 місяців тому

      You can read here for using cookies and headers in middleware functions => nextjs.org/docs/app/building-your-application/routing/middleware#using-cookies

  • @pot42
    @pot42 7 місяців тому +1

    The fact that middleware chaining is still not managed out-of-the-box still baffles me. This is a lot of custom code for features that are widely used in many apps :(

    • @hamedbahram
      @hamedbahram  7 місяців тому +2

      I agree! This is an area that needs improvement.

  • @mohdsahil226
    @mohdsahil226 9 місяців тому +1

    Thank you for this great tutorial. Would you please help me on this. I am not able load images from public folder. I added this code to i18n middleware. still not working!
    if (
    request.nextUrl.pathname.startsWith("/_next") ||
    request.nextUrl.pathname.includes("/public/") ||
    )
    return;

    • @hamedbahram
      @hamedbahram  9 місяців тому

      The content of your public folder is served at the root `/` of your app. You can put your images in the `images` folder inside the `public` folder and exclude the `images` folder in your middleware config matcher.

    • @mohdsahil226
      @mohdsahil226 9 місяців тому

      Thank you for your quick reply, but creating public/images folder was giving error "unable to optimized images + logo.svg was not loading"
      I did this inside i18n middleware and it worked. I know whether it is right.
      if (pathname.includes(".")) return; // exclude all files in the public folder
      Do you have any promo code for your courses or live classes? I want to take your live classes.

    • @hamedbahram
      @hamedbahram  9 місяців тому

      @@mohdsahil226 Glad you were able to figure it out. I don't have any live classes but you can take my NextJs course which is currently on a promotion.

  • @hassanallen1823
    @hassanallen1823 7 місяців тому

    hi Hamed jan, I am having trouble with custom login redirect from dashboard, since I add /signin on nextauth pages it always redirect me en/signin

    • @hassanallen1823
      @hassanallen1823 7 місяців тому +1

      this is what i comeup with const locale = pathname.split("/")[1];
      const signInUrl = new URL(`/${locale}/signin`, request.url);

    • @hamedbahram
      @hamedbahram  7 місяців тому

      Hmm 🤔 I see. That makes sense.

  • @andrewfowl111
    @andrewfowl111 9 місяців тому +1

    Is this Auth.js v4 or v5?

    • @hamedbahram
      @hamedbahram  9 місяців тому

      This is v4. V5 is not out yet

  • @kamill34
    @kamill34 9 місяців тому

    very cool!
    I been trying apply middleware for uploading large files (middleware should combine small chunks into one File). Can you help me with implement this code ?

    • @hamedbahram
      @hamedbahram  9 місяців тому

      Tell me what you're trying to do.

    • @kamill34
      @kamill34 9 місяців тому

      @@hamedbahram
      I'm writing an application in Next.js that needs to process a .mdb file and display the results as charts on the client-side. Unfortunately, it's not possible to process the .mdb file on the client-side, and it needs to be done on the server-side. I've set up an input File and an API that receives and processes the file. Locally, it works without any issues, but on the server, like Vercel for example, there is a maximum file size limit of 4.5 MB for file uploads. To work around this, you can split the file (as it's a Blob) into smaller chunks. To handle file uploads on the server, you'll need a middleware. In Express, there are several libraries that can handle such requests, such as busboy or formidable. I'm not sure how to implement this or if it's even possible. What are your thoughts? Do you think it's possible to do this directly in Next.js?

  • @hkhdev95
    @hkhdev95 9 місяців тому

    Please Good advice for you, your are going too too too much deep in describe the ideas and that's not good at all. every video for you have a good idea, but I really feel bored in all your videos, not just one. this video you can describe it in just 15mins.

    • @hamedbahram
      @hamedbahram  9 місяців тому

      Thanks for your suggestion.