User accounts added
	
		
			
	
		
	
	
		
			
				
	
				continuous-integration/drone/push Build is failing
				
					Details
				
			
		
	
				
					
				
			
				
	
				continuous-integration/drone/push Build is failing
				
					Details
				
			
		
	- Adds user login with flask-login. - Adds basic Toast flash messages using toastify.jsmaster
							parent
							
								
									910ad0aedf
								
							
						
					
					
						commit
						d728bda7be
					
				@ -0,0 +1,5 @@
 | 
				
			|||||||
 | 
					# Notes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Toasts using Toastify.js
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[In depth example of a Toast](https://www.cssscript.com/simple-vanilla-javascript-toast-notification-library-toastify/)
 | 
				
			||||||
@ -0,0 +1,19 @@
 | 
				
			|||||||
 | 
					.toast-basic {
 | 
				
			||||||
 | 
					    padding: 12px 20px;
 | 
				
			||||||
 | 
					    color: #ffffff;
 | 
				
			||||||
 | 
					    display: inline-block;
 | 
				
			||||||
 | 
					    box-shadow: 0 3px 6px -1px rgba(0, 0, 0, 0.12), 0 10px 36px -4px rgba(77, 96, 232, 0.3);
 | 
				
			||||||
 | 
					    background: -webkit-linear-gradient(315deg, #73a5ff, #5477f5);
 | 
				
			||||||
 | 
					    background: linear-gradient(135deg, #73a5ff, #5477f5);
 | 
				
			||||||
 | 
					    position: fixed;
 | 
				
			||||||
 | 
					    top: -150px;
 | 
				
			||||||
 | 
					    right: 15px;
 | 
				
			||||||
 | 
					    opacity: 0;
 | 
				
			||||||
 | 
					    transition: all 0.4s cubic-bezier(0.215, 0.61, 0.355, 1);
 | 
				
			||||||
 | 
					    border-radius: 2px;
 | 
				
			||||||
 | 
					    cursor: pointer;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.toastify.on {
 | 
				
			||||||
 | 
					    opacity: 1;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,6 @@
 | 
				
			|||||||
 | 
					This favicon was generated using the following font:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Font Title: Leckerli One
 | 
				
			||||||
 | 
					- Font Author: Copyright (c) 2011 Gesine Todt (www.gesine-todt.de hallo@gesine-todt.de), with Reserved Font Names "Leckerli"
 | 
				
			||||||
 | 
					- Font Source: http://fonts.gstatic.com/s/leckerlione/v20/V8mCoQH8VCsNttEnxnGQ-1itLZxcBtItFw.ttf
 | 
				
			||||||
 | 
					- Font License: SIL Open Font License, 1.1 (http://scripts.sil.org/OFL))
 | 
				
			||||||
											
												Binary file not shown.
											
										
									
								| 
		 After Width: | Height: | Size: 12 KiB  | 
											
												Binary file not shown.
											
										
									
								| 
		 After Width: | Height: | Size: 34 KiB  | 
											
												Binary file not shown.
											
										
									
								| 
		 After Width: | Height: | Size: 11 KiB  | 
											
												Binary file not shown.
											
										
									
								| 
		 After Width: | Height: | Size: 673 B  | 
											
												Binary file not shown.
											
										
									
								| 
		 After Width: | Height: | Size: 1.4 KiB  | 
											
												Binary file not shown.
											
										
									
								| 
		 After Width: | Height: | Size: 15 KiB  | 
@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
 | 
				
			||||||
@ -0,0 +1,23 @@
 | 
				
			|||||||
 | 
					function getFlashMessages() {
 | 
				
			||||||
 | 
					    let elements = document.querySelectorAll('[x-flash-message]');
 | 
				
			||||||
 | 
					    let contentArray = Array.from(elements).map(el => el.innerHTML)
 | 
				
			||||||
 | 
					    return contentArray;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function showFlashMessages(messages) {
 | 
				
			||||||
 | 
					    messages.forEach(message => {
 | 
				
			||||||
 | 
					        Toastify({
 | 
				
			||||||
 | 
					            text: message,
 | 
				
			||||||
 | 
					            className: "toast-basic",
 | 
				
			||||||
 | 
					            duration: 3000,
 | 
				
			||||||
 | 
					            newWindow: true,
 | 
				
			||||||
 | 
					            close: false, // Show toast close icon
 | 
				
			||||||
 | 
					            gravity: "top", // `top` or `bottom`
 | 
				
			||||||
 | 
					            position: "center", // `left`, `center` or `right`
 | 
				
			||||||
 | 
					            stopOnFocus: true, // Prevents dismissing of toast on hover
 | 
				
			||||||
 | 
					        }).showToast();
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					showFlashMessages(getFlashMessages())
 | 
				
			||||||
@ -0,0 +1,445 @@
 | 
				
			|||||||
 | 
					/*!
 | 
				
			||||||
 | 
					 * Toastify js 1.12.0
 | 
				
			||||||
 | 
					 * https://github.com/apvarun/toastify-js
 | 
				
			||||||
 | 
					 * @license MIT licensed
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Copyright (C) 2018 Varun A P
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					(function (root, factory) {
 | 
				
			||||||
 | 
					    if (typeof module === "object" && module.exports) {
 | 
				
			||||||
 | 
					        module.exports = factory();
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        root.Toastify = factory();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					})(this, function (global) {
 | 
				
			||||||
 | 
					    // Object initialization
 | 
				
			||||||
 | 
					    var Toastify = function (options) {
 | 
				
			||||||
 | 
					        // Returning a new init object
 | 
				
			||||||
 | 
					        return new Toastify.lib.init(options);
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					        // Library version
 | 
				
			||||||
 | 
					        version = "1.12.0";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Set the default global options
 | 
				
			||||||
 | 
					    Toastify.defaults = {
 | 
				
			||||||
 | 
					        oldestFirst: true,
 | 
				
			||||||
 | 
					        text: "Toastify is awesome!",
 | 
				
			||||||
 | 
					        node: undefined,
 | 
				
			||||||
 | 
					        duration: 3000,
 | 
				
			||||||
 | 
					        selector: undefined,
 | 
				
			||||||
 | 
					        callback: function () {
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        destination: undefined,
 | 
				
			||||||
 | 
					        newWindow: false,
 | 
				
			||||||
 | 
					        close: false,
 | 
				
			||||||
 | 
					        gravity: "toastify-top",
 | 
				
			||||||
 | 
					        positionLeft: false,
 | 
				
			||||||
 | 
					        position: '',
 | 
				
			||||||
 | 
					        backgroundColor: '',
 | 
				
			||||||
 | 
					        avatar: "",
 | 
				
			||||||
 | 
					        className: "",
 | 
				
			||||||
 | 
					        stopOnFocus: true,
 | 
				
			||||||
 | 
					        onClick: function () {
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        offset: { x: 0, y: 0 },
 | 
				
			||||||
 | 
					        escapeMarkup: true,
 | 
				
			||||||
 | 
					        ariaLive: 'polite',
 | 
				
			||||||
 | 
					        style: { background: '' }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Defining the prototype of the object
 | 
				
			||||||
 | 
					    Toastify.lib = Toastify.prototype = {
 | 
				
			||||||
 | 
					        toastify: version,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        constructor: Toastify,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Initializing the object with required parameters
 | 
				
			||||||
 | 
					        init: function (options) {
 | 
				
			||||||
 | 
					            // Verifying and validating the input object
 | 
				
			||||||
 | 
					            if (!options) {
 | 
				
			||||||
 | 
					                options = {};
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Creating the options object
 | 
				
			||||||
 | 
					            this.options = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            this.toastElement = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Validating the options
 | 
				
			||||||
 | 
					            this.options.text = options.text || Toastify.defaults.text; // Display message
 | 
				
			||||||
 | 
					            this.options.node = options.node || Toastify.defaults.node;  // Display content as node
 | 
				
			||||||
 | 
					            this.options.duration = options.duration === 0 ? 0 : options.duration || Toastify.defaults.duration; // Display duration
 | 
				
			||||||
 | 
					            this.options.selector = options.selector || Toastify.defaults.selector; // Parent selector
 | 
				
			||||||
 | 
					            this.options.callback = options.callback || Toastify.defaults.callback; // Callback after display
 | 
				
			||||||
 | 
					            this.options.destination = options.destination || Toastify.defaults.destination; // On-click destination
 | 
				
			||||||
 | 
					            this.options.newWindow = options.newWindow || Toastify.defaults.newWindow; // Open destination in new window
 | 
				
			||||||
 | 
					            this.options.close = options.close || Toastify.defaults.close; // Show toast close icon
 | 
				
			||||||
 | 
					            this.options.gravity = options.gravity === "bottom" ? "toastify-bottom" : Toastify.defaults.gravity; // toast position - top or bottom
 | 
				
			||||||
 | 
					            this.options.positionLeft = options.positionLeft || Toastify.defaults.positionLeft; // toast position - left or right
 | 
				
			||||||
 | 
					            this.options.position = options.position || Toastify.defaults.position; // toast position - left or right
 | 
				
			||||||
 | 
					            this.options.backgroundColor = options.backgroundColor || Toastify.defaults.backgroundColor; // toast background color
 | 
				
			||||||
 | 
					            this.options.avatar = options.avatar || Toastify.defaults.avatar; // img element src - url or a path
 | 
				
			||||||
 | 
					            this.options.className = options.className || Toastify.defaults.className; // additional class names for the toast
 | 
				
			||||||
 | 
					            this.options.stopOnFocus = options.stopOnFocus === undefined ? Toastify.defaults.stopOnFocus : options.stopOnFocus; // stop timeout on focus
 | 
				
			||||||
 | 
					            this.options.onClick = options.onClick || Toastify.defaults.onClick; // Callback after click
 | 
				
			||||||
 | 
					            this.options.offset = options.offset || Toastify.defaults.offset; // toast offset
 | 
				
			||||||
 | 
					            this.options.escapeMarkup = options.escapeMarkup !== undefined ? options.escapeMarkup : Toastify.defaults.escapeMarkup;
 | 
				
			||||||
 | 
					            this.options.ariaLive = options.ariaLive || Toastify.defaults.ariaLive;
 | 
				
			||||||
 | 
					            this.options.style = options.style || Toastify.defaults.style;
 | 
				
			||||||
 | 
					            if (options.backgroundColor) {
 | 
				
			||||||
 | 
					                this.options.style.background = options.backgroundColor;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Returning the current object for chaining functions
 | 
				
			||||||
 | 
					            return this;
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Building the DOM element
 | 
				
			||||||
 | 
					        buildToast: function () {
 | 
				
			||||||
 | 
					            // Validating if the options are defined
 | 
				
			||||||
 | 
					            if (!this.options) {
 | 
				
			||||||
 | 
					                throw "Toastify is not initialized";
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Creating the DOM object
 | 
				
			||||||
 | 
					            var divElement = document.createElement("div");
 | 
				
			||||||
 | 
					            divElement.className = "toastify on " + this.options.className;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Positioning toast to left or right or center
 | 
				
			||||||
 | 
					            if (!!this.options.position) {
 | 
				
			||||||
 | 
					                divElement.className += " toastify-" + this.options.position;
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                // To be depreciated in further versions
 | 
				
			||||||
 | 
					                if (this.options.positionLeft === true) {
 | 
				
			||||||
 | 
					                    divElement.className += " toastify-left";
 | 
				
			||||||
 | 
					                    console.warn('Property `positionLeft` will be depreciated in further versions. Please use `position` instead.')
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    // Default position
 | 
				
			||||||
 | 
					                    divElement.className += " toastify-right";
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Assigning gravity of element
 | 
				
			||||||
 | 
					            divElement.className += " " + this.options.gravity;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (this.options.backgroundColor) {
 | 
				
			||||||
 | 
					                // This is being deprecated in favor of using the style HTML DOM property
 | 
				
			||||||
 | 
					                console.warn('DEPRECATION NOTICE: "backgroundColor" is being deprecated. Please use the "style.background" property.');
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Loop through our style object and apply styles to divElement
 | 
				
			||||||
 | 
					            for (var property in this.options.style) {
 | 
				
			||||||
 | 
					                divElement.style[property] = this.options.style[property];
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Announce the toast to screen readers
 | 
				
			||||||
 | 
					            if (this.options.ariaLive) {
 | 
				
			||||||
 | 
					                divElement.setAttribute('aria-live', this.options.ariaLive)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Adding the toast message/node
 | 
				
			||||||
 | 
					            if (this.options.node && this.options.node.nodeType === Node.ELEMENT_NODE) {
 | 
				
			||||||
 | 
					                // If we have a valid node, we insert it
 | 
				
			||||||
 | 
					                divElement.appendChild(this.options.node)
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                if (this.options.escapeMarkup) {
 | 
				
			||||||
 | 
					                    divElement.innerText = this.options.text;
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    divElement.innerHTML = this.options.text;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (this.options.avatar !== "") {
 | 
				
			||||||
 | 
					                    var avatarElement = document.createElement("img");
 | 
				
			||||||
 | 
					                    avatarElement.src = this.options.avatar;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    avatarElement.className = "toastify-avatar";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    if (this.options.position == "left" || this.options.positionLeft === true) {
 | 
				
			||||||
 | 
					                        // Adding close icon on the left of content
 | 
				
			||||||
 | 
					                        divElement.appendChild(avatarElement);
 | 
				
			||||||
 | 
					                    } else {
 | 
				
			||||||
 | 
					                        // Adding close icon on the right of content
 | 
				
			||||||
 | 
					                        divElement.insertAdjacentElement("afterbegin", avatarElement);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Adding a close icon to the toast
 | 
				
			||||||
 | 
					            if (this.options.close === true) {
 | 
				
			||||||
 | 
					                // Create a span for close element
 | 
				
			||||||
 | 
					                var closeElement = document.createElement("button");
 | 
				
			||||||
 | 
					                closeElement.type = "button";
 | 
				
			||||||
 | 
					                closeElement.setAttribute("aria-label", "Close");
 | 
				
			||||||
 | 
					                closeElement.className = "toast-close";
 | 
				
			||||||
 | 
					                closeElement.innerHTML = "✖";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // Triggering the removal of toast from DOM on close click
 | 
				
			||||||
 | 
					                closeElement.addEventListener(
 | 
				
			||||||
 | 
					                    "click",
 | 
				
			||||||
 | 
					                    function (event) {
 | 
				
			||||||
 | 
					                        event.stopPropagation();
 | 
				
			||||||
 | 
					                        this.removeElement(this.toastElement);
 | 
				
			||||||
 | 
					                        window.clearTimeout(this.toastElement.timeOutValue);
 | 
				
			||||||
 | 
					                    }.bind(this)
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                //Calculating screen width
 | 
				
			||||||
 | 
					                var width = window.innerWidth > 0 ? window.innerWidth : screen.width;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // Adding the close icon to the toast element
 | 
				
			||||||
 | 
					                // Display on the right if screen width is less than or equal to 360px
 | 
				
			||||||
 | 
					                if ((this.options.position == "left" || this.options.positionLeft === true) && width > 360) {
 | 
				
			||||||
 | 
					                    // Adding close icon on the left of content
 | 
				
			||||||
 | 
					                    divElement.insertAdjacentElement("afterbegin", closeElement);
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    // Adding close icon on the right of content
 | 
				
			||||||
 | 
					                    divElement.appendChild(closeElement);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Clear timeout while toast is focused
 | 
				
			||||||
 | 
					            if (this.options.stopOnFocus && this.options.duration > 0) {
 | 
				
			||||||
 | 
					                var self = this;
 | 
				
			||||||
 | 
					                // stop countdown
 | 
				
			||||||
 | 
					                divElement.addEventListener(
 | 
				
			||||||
 | 
					                    "mouseover",
 | 
				
			||||||
 | 
					                    function (event) {
 | 
				
			||||||
 | 
					                        window.clearTimeout(divElement.timeOutValue);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					                // add back the timeout
 | 
				
			||||||
 | 
					                divElement.addEventListener(
 | 
				
			||||||
 | 
					                    "mouseleave",
 | 
				
			||||||
 | 
					                    function () {
 | 
				
			||||||
 | 
					                        divElement.timeOutValue = window.setTimeout(
 | 
				
			||||||
 | 
					                            function () {
 | 
				
			||||||
 | 
					                                // Remove the toast from DOM
 | 
				
			||||||
 | 
					                                self.removeElement(divElement);
 | 
				
			||||||
 | 
					                            },
 | 
				
			||||||
 | 
					                            self.options.duration
 | 
				
			||||||
 | 
					                        )
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Adding an on-click destination path
 | 
				
			||||||
 | 
					            if (typeof this.options.destination !== "undefined") {
 | 
				
			||||||
 | 
					                divElement.addEventListener(
 | 
				
			||||||
 | 
					                    "click",
 | 
				
			||||||
 | 
					                    function (event) {
 | 
				
			||||||
 | 
					                        event.stopPropagation();
 | 
				
			||||||
 | 
					                        if (this.options.newWindow === true) {
 | 
				
			||||||
 | 
					                            window.open(this.options.destination, "_blank");
 | 
				
			||||||
 | 
					                        } else {
 | 
				
			||||||
 | 
					                            window.location = this.options.destination;
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }.bind(this)
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (typeof this.options.onClick === "function" && typeof this.options.destination === "undefined") {
 | 
				
			||||||
 | 
					                divElement.addEventListener(
 | 
				
			||||||
 | 
					                    "click",
 | 
				
			||||||
 | 
					                    function (event) {
 | 
				
			||||||
 | 
					                        event.stopPropagation();
 | 
				
			||||||
 | 
					                        this.options.onClick();
 | 
				
			||||||
 | 
					                    }.bind(this)
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Adding offset
 | 
				
			||||||
 | 
					            if (typeof this.options.offset === "object") {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                var x = getAxisOffsetAValue("x", this.options);
 | 
				
			||||||
 | 
					                var y = getAxisOffsetAValue("y", this.options);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                var xOffset = this.options.position == "left" ? x : "-" + x;
 | 
				
			||||||
 | 
					                var yOffset = this.options.gravity == "toastify-top" ? y : "-" + y;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                divElement.style.transform = "translate(" + xOffset + "," + yOffset + ")";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Returning the generated element
 | 
				
			||||||
 | 
					            return divElement;
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Displaying the toast
 | 
				
			||||||
 | 
					        showToast: function () {
 | 
				
			||||||
 | 
					            // Creating the DOM object for the toast
 | 
				
			||||||
 | 
					            this.toastElement = this.buildToast();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Getting the root element to with the toast needs to be added
 | 
				
			||||||
 | 
					            var rootElement;
 | 
				
			||||||
 | 
					            if (typeof this.options.selector === "string") {
 | 
				
			||||||
 | 
					                rootElement = document.getElementById(this.options.selector);
 | 
				
			||||||
 | 
					            } else if (this.options.selector instanceof HTMLElement || (typeof ShadowRoot !== 'undefined' && this.options.selector instanceof ShadowRoot)) {
 | 
				
			||||||
 | 
					                rootElement = this.options.selector;
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                rootElement = document.body;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Validating if root element is present in DOM
 | 
				
			||||||
 | 
					            if (!rootElement) {
 | 
				
			||||||
 | 
					                throw "Root element is not defined";
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Adding the DOM element
 | 
				
			||||||
 | 
					            var elementToInsert = Toastify.defaults.oldestFirst ? rootElement.firstChild : rootElement.lastChild;
 | 
				
			||||||
 | 
					            rootElement.insertBefore(this.toastElement, elementToInsert);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Repositioning the toasts in case multiple toasts are present
 | 
				
			||||||
 | 
					            Toastify.reposition();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (this.options.duration > 0) {
 | 
				
			||||||
 | 
					                this.toastElement.timeOutValue = window.setTimeout(
 | 
				
			||||||
 | 
					                    function () {
 | 
				
			||||||
 | 
					                        // Remove the toast from DOM
 | 
				
			||||||
 | 
					                        this.removeElement(this.toastElement);
 | 
				
			||||||
 | 
					                    }.bind(this),
 | 
				
			||||||
 | 
					                    this.options.duration
 | 
				
			||||||
 | 
					                ); // Binding `this` for function invocation
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Supporting function chaining
 | 
				
			||||||
 | 
					            return this;
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        hideToast: function () {
 | 
				
			||||||
 | 
					            if (this.toastElement.timeOutValue) {
 | 
				
			||||||
 | 
					                clearTimeout(this.toastElement.timeOutValue);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            this.removeElement(this.toastElement);
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Removing the element from the DOM
 | 
				
			||||||
 | 
					        removeElement: function (toastElement) {
 | 
				
			||||||
 | 
					            // Hiding the element
 | 
				
			||||||
 | 
					            // toastElement.classList.remove("on");
 | 
				
			||||||
 | 
					            toastElement.className = toastElement.className.replace(" on", "");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Removing the element from DOM after transition end
 | 
				
			||||||
 | 
					            window.setTimeout(
 | 
				
			||||||
 | 
					                function () {
 | 
				
			||||||
 | 
					                    // remove options node if any
 | 
				
			||||||
 | 
					                    if (this.options.node && this.options.node.parentNode) {
 | 
				
			||||||
 | 
					                        this.options.node.parentNode.removeChild(this.options.node);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    // Remove the element from the DOM, only when the parent node was not removed before.
 | 
				
			||||||
 | 
					                    if (toastElement.parentNode) {
 | 
				
			||||||
 | 
					                        toastElement.parentNode.removeChild(toastElement);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    // Calling the callback function
 | 
				
			||||||
 | 
					                    this.options.callback.call(toastElement);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    // Repositioning the toasts again
 | 
				
			||||||
 | 
					                    Toastify.reposition();
 | 
				
			||||||
 | 
					                }.bind(this),
 | 
				
			||||||
 | 
					                400
 | 
				
			||||||
 | 
					            ); // Binding `this` for function invocation
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Positioning the toasts on the DOM
 | 
				
			||||||
 | 
					    Toastify.reposition = function () {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Top margins with gravity
 | 
				
			||||||
 | 
					        var topLeftOffsetSize = {
 | 
				
			||||||
 | 
					            top: 15,
 | 
				
			||||||
 | 
					            bottom: 15,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        var topRightOffsetSize = {
 | 
				
			||||||
 | 
					            top: 15,
 | 
				
			||||||
 | 
					            bottom: 15,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        var offsetSize = {
 | 
				
			||||||
 | 
					            top: 15,
 | 
				
			||||||
 | 
					            bottom: 15,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Get all toast messages on the DOM
 | 
				
			||||||
 | 
					        var allToasts = document.getElementsByClassName("toastify");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var classUsed;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Modifying the position of each toast element
 | 
				
			||||||
 | 
					        for (var i = 0; i < allToasts.length; i++) {
 | 
				
			||||||
 | 
					            // Getting the applied gravity
 | 
				
			||||||
 | 
					            if (containsClass(allToasts[i], "toastify-top") === true) {
 | 
				
			||||||
 | 
					                classUsed = "toastify-top";
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                classUsed = "toastify-bottom";
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var height = allToasts[i].offsetHeight;
 | 
				
			||||||
 | 
					            classUsed = classUsed.substr(9, classUsed.length - 1)
 | 
				
			||||||
 | 
					            // Spacing between toasts
 | 
				
			||||||
 | 
					            var offset = 15;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var width = window.innerWidth > 0 ? window.innerWidth : screen.width;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Show toast in center if screen with less than or equal to 360px
 | 
				
			||||||
 | 
					            if (width <= 360) {
 | 
				
			||||||
 | 
					                // Setting the position
 | 
				
			||||||
 | 
					                allToasts[i].style[classUsed] = offsetSize[classUsed] + "px";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                offsetSize[classUsed] += height + offset;
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                if (containsClass(allToasts[i], "toastify-left") === true) {
 | 
				
			||||||
 | 
					                    // Setting the position
 | 
				
			||||||
 | 
					                    allToasts[i].style[classUsed] = topLeftOffsetSize[classUsed] + "px";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    topLeftOffsetSize[classUsed] += height + offset;
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    // Setting the position
 | 
				
			||||||
 | 
					                    allToasts[i].style[classUsed] = topRightOffsetSize[classUsed] + "px";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    topRightOffsetSize[classUsed] += height + offset;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Supporting function chaining
 | 
				
			||||||
 | 
					        return this;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Helper function to get offset.
 | 
				
			||||||
 | 
					    function getAxisOffsetAValue(axis, options) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (options.offset[axis]) {
 | 
				
			||||||
 | 
					            if (isNaN(options.offset[axis])) {
 | 
				
			||||||
 | 
					                return options.offset[axis];
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            else {
 | 
				
			||||||
 | 
					                return options.offset[axis] + 'px';
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return '0px';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function containsClass(elem, yourClass) {
 | 
				
			||||||
 | 
					        if (!elem || typeof yourClass !== "string") {
 | 
				
			||||||
 | 
					            return false;
 | 
				
			||||||
 | 
					        } else if (
 | 
				
			||||||
 | 
					            elem.className &&
 | 
				
			||||||
 | 
					            elem.className
 | 
				
			||||||
 | 
					                .trim()
 | 
				
			||||||
 | 
					                .split(/\s+/gi)
 | 
				
			||||||
 | 
					                .indexOf(yourClass) > -1
 | 
				
			||||||
 | 
					        ) {
 | 
				
			||||||
 | 
					            return true;
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            return false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Setting up the prototype for the init object
 | 
				
			||||||
 | 
					    Toastify.lib.init.prototype = Toastify.lib;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Returning the Toastify function to be assigned to the window object/module
 | 
				
			||||||
 | 
					    return Toastify;
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					{% extends 'base.html' %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% block content %}
 | 
				
			||||||
 | 
					  <form action="/user/login" method="post">
 | 
				
			||||||
 | 
					    <label for="username">Email:</label>
 | 
				
			||||||
 | 
					    <input type="text" name="email" >
 | 
				
			||||||
 | 
					    <label for="password">Password:</label>
 | 
				
			||||||
 | 
					    <input type="password" name="password">
 | 
				
			||||||
 | 
					    <button type="submit">Sign In</button>
 | 
				
			||||||
 | 
					  </form>
 | 
				
			||||||
 | 
					{% endblock %}
 | 
				
			||||||
@ -0,0 +1,14 @@
 | 
				
			|||||||
 | 
					{% extends 'base.html' %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% block content %}
 | 
				
			||||||
 | 
					<form action="/user" method="post">
 | 
				
			||||||
 | 
					    <h1>Sign Up</h1>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <input type="text" name="name" placeholder="Username">
 | 
				
			||||||
 | 
					    <input type="email" name="email" placeholder="Email">
 | 
				
			||||||
 | 
					    <input type="password" name="password" placeholder="Password">
 | 
				
			||||||
 | 
					    <input type="password" name="confirm_password" placeholder="Confirm Password">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <button type="submit">Sign Up</button>
 | 
				
			||||||
 | 
					  </form>
 | 
				
			||||||
 | 
					{% endblock %}
 | 
				
			||||||
@ -1,18 +1,76 @@
 | 
				
			|||||||
 | 
					import logging
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from flask import Blueprint
 | 
					from flask import Blueprint
 | 
				
			||||||
 | 
					from flask import abort
 | 
				
			||||||
 | 
					from flask import flash
 | 
				
			||||||
 | 
					from flask import redirect
 | 
				
			||||||
 | 
					from flask import render_template
 | 
				
			||||||
 | 
					from flask import request
 | 
				
			||||||
 | 
					from flask import url_for
 | 
				
			||||||
 | 
					from flask_login import login_user as fl_login_user
 | 
				
			||||||
 | 
					from flask_login import logout_user as fl_logout_user
 | 
				
			||||||
 | 
					from pydantic import BaseModel
 | 
				
			||||||
 | 
					from pydantic import ValidationError
 | 
				
			||||||
 | 
					from sqlalchemy import select
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from htmx_contact import Session
 | 
				
			||||||
 | 
					from htmx_contact.models import User
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bp = Blueprint("user", __name__, url_prefix="/user")
 | 
					bp = Blueprint("user", __name__, url_prefix="/user")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					logger = logging.getLogger(__name__)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class LoginValidator(BaseModel):
 | 
				
			||||||
 | 
					    """Used to validate user login"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    email: str
 | 
				
			||||||
 | 
					    password: str
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@bp.route("/login", methods=["GET", "POST"])
 | 
					@bp.route("/login", methods=["GET", "POST"])
 | 
				
			||||||
def user_login():
 | 
					def user_login():
 | 
				
			||||||
    pass
 | 
					    if request.method == "POST":
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            data = LoginValidator.model_validate(dict(request.form))
 | 
				
			||||||
 | 
					        except ValidationError:
 | 
				
			||||||
 | 
					            logger.warning("User input failed validation")
 | 
				
			||||||
 | 
					            abort(422)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        logger.info(f"Received login request from {data.email}")
 | 
				
			||||||
 | 
					        data = LoginValidator.model_validate(dict(request.form))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        with Session() as session:
 | 
				
			||||||
 | 
					            select_user_stmt = select(User).where(User.primary_email == data.email)
 | 
				
			||||||
 | 
					            user = session.scalar(select_user_stmt)
 | 
				
			||||||
 | 
					            if user is not None and user.check_password(data.password):
 | 
				
			||||||
 | 
					                # Login and redirect from where they came from
 | 
				
			||||||
 | 
					                fl_login_user(user=user)
 | 
				
			||||||
 | 
					                flash("Welcome back {}.".format(user.username))
 | 
				
			||||||
 | 
					                return redirect(request.args.get("next") or url_for("main.contacts"))
 | 
				
			||||||
 | 
					            # User was None or password was incorrect
 | 
				
			||||||
 | 
					            flash("Incorrect username or password")
 | 
				
			||||||
 | 
					        return render_template("login.html")
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        return render_template("login.html")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@bp.route("/logout", methods=["GET"])
 | 
					@bp.route("/logout", methods=["GET"])
 | 
				
			||||||
def user_logout():
 | 
					def user_logout():
 | 
				
			||||||
    pass
 | 
					    fl_logout_user()
 | 
				
			||||||
 | 
					    """Logs a user out of thier session"""
 | 
				
			||||||
 | 
					    return redirect(url_for('main.contacts'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@bp.route("/sign-up")
 | 
					@bp.route("/sign-up", methods=["GET"])
 | 
				
			||||||
def user_sign_up():
 | 
					def user_sign_up():
 | 
				
			||||||
    pass
 | 
					    """Renders the signup form to the user"""
 | 
				
			||||||
 | 
					    return render_template("sign-up.html")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@bp.route("/", methods=["POST"])
 | 
				
			||||||
 | 
					def create_user():
 | 
				
			||||||
 | 
					    """Creates a new user"""
 | 
				
			||||||
 | 
					    # create the user.
 | 
				
			||||||
 | 
					    # Add message flash
 | 
				
			||||||
 | 
					    return redirect(url_for("user.user_login"))
 | 
				
			||||||
 | 
				
			|||||||
					Loading…
					
					
				
		Reference in New Issue