Do I need separate Graphics and Transfer commands?
Your question seems... very confused. The "challenge" you tried to do specifically stated to "use a different queue family specifically for transfer operations". But that's not what your code is trying to do; you used the same queue family.

The point of creating a "transfer queue" is to allow you to perform transfer operations without having to submit them as part of another batch. vkQueueSubmit cannot be called on the same queue from multiple threads. So if one thread builds some transfer work, but the other thread is doing some graphics work, if you only have one queue then the two threads must have some communication mechanism to submit that work.

If you have a transfer queue, that inter-thread communication is no longer necessary. Or at least, it is no longer necessary for the basic submission action. Obviously whichever batch of work that uses the results of the transfer commands will have to receive a semaphore to wait on from the thread that submitted the transfer work. But that is usually a less contentious inter-thread communication path, which can easily be done with a lockless queue of some kind.

The challenge in question is specifically talking about a queue family that only has the VK_QUEUE_TRANSFER_BIT flag set (or more specifically, does not have graphics or compute capabilities). Such transfer-only queues are usually tied to specialized hardware that (potentially) perform transfers faster than a graphics queue, but likely with extra limitations (as defined in VkQueueFamilyProperties::minImageTransferGranularity and other properties).

So basically, you're doing the challenge wrong.

Do keep in mind that Vulkan does not require implementations to provide a transfer-only queue. So if you want to take advantage of the presence of such a queue, you also have to take into account the possibility that no such queue exists.

Now, here's the thing: the term "queue family" is used because you can potentially get multiple queues from the same family. VkQueueFamilyProperties::queueCount tells you how many queues a particular family allows you to get.

So if a GPU does not provide a dedicated transfer-only queue, but you still want the asynchronous benefits of submitting transfer operations outside of non-transfer work, you can create a second graphics queue.

Of course, Vulkan also doesn't require implementations to provide more than one graphics or compute queue. So again, you have to have a synchronous fallback plan in case the hardware only provides you with one queue.