you need to put the form processing code and the corresponding form on the same page. what you have now takes almost two times the amount of code and by passing messages through the url, it is open to phishing attacks and cross site scripting.
the code for any page should be laid out in this general order -
initialization
post method form processing
get method business logic - get/produce the data needed to display the page
html document
here's a list of points that will help you with your code -
validate all resulting web pages at validator.w3.org
since you will be putting the form and the form processing code on the same page, you can leave the entire action='...' attribute out of the form tag to cause the form to submit to the same page it is on.
if you put the <label></label> tags around the form field they belong with, you can leave out the for='...' and corresponding id='...' attributes, which you don't have anyways.
use 'require' for things your code must have for it to work.
if you are building multiple web pages, use 'require' for the common parts so that you are not repeating code over and over.
the post method form processing code should first detect if a post method form was submitted. in your current code, writing out a series of isset() tests, you are creating bespoke code that must be changed every time you do something different. also, if you had a form with 30 or a 100 fields, would writting out a series of 30 or a 100 isset() statements seem like a good use of your time?
forget about this validate() function. it is improperly named and the only thing it is doing that is proper is trimming the data.
don't create a bunch of discrete variables for nothing. instead keep the form data as a set in a php array variable, then operate on elements in this array variable throughout the rest of the code.
your post method form processing code should trim, than validate all input data, storing user/validation errors in an array using the field name as the array index. after the end of all the validation logic, if there are no errors (the array will be empty), use the submitted form data.
the only redirect you should have in your code should be upon successful completion of the post method form processing and it should be to the exact same url of the current page to cause a get request for that page.
you should not store plain text passwords. use php's password_hash() and password_verify()
you cannot successfully echo or output content before a header() redirect.
the only value that you should store in a session variable upon successful login is the user's id. you should query on each page request to get any other user information, such as their username or permissions.
if there are user/validation errors, and since you are now putting the form and the form processing code on the same page, your code would continue on to display the html document, display any errors, re-display the form, populating any appropriate fields with there existing values so that the user doesn't need to keep reentering data over and over.
any dynamic value that you output in a html context, should have htmlentities() applied to it when you output it, to help prevent cross site scripting.
when conditional failure code is much shorter then the success code, if you complement the condition being tested and put the failure code first, it is easier to follow what your code is doing. also, any time you have an exit/die in a conditional branch of code, you don't need to make any following branch 'conditional' since exaction won't continue past that point.
don't let visitors (and hackers) know when internal errors have occurred, such as database connection errors. instead, use exceptions for errors and in most cases simply let php catch and handle any exception, where php will use its error related settings to control what happens with the actual error information. also, in the latest php versions, a mysqli connection error automatically uses exceptions, so your existing connection error handling logic will never get executed upon an error and might as well be removed, simplifying the code.
the file upload form processing must detect if the $_FILES array is not empty before referencing any of the $_FILES data. if total posted form data exceeds the post_max_size setting, both the $_POST and $_FILES arrays will be empty, you must detect this condition and setup a message for the user that the size of the uploaded file was too large. i suspect this is the reason why you are getting an empty $_FILES array and the undefined array index error.
after you have detected that there is data in $_FILES, you must test the ['error'] element. there will only be valid data in the other elements if the error element is a zero (UPLOAD_ERR_OK) you should also setup user messages for the other errors that the user has control over. you can find the definition of the upload errors in the php documentation.