From ba83922660dade77dcb96e8ba9c73cfcba8c2b81 Mon Sep 17 00:00:00 2001
From: retoor <retoor@molodetz.nl>
Date: Fri, 24 Jan 2025 03:28:43 +0100
Subject: [PATCH] Progress.

---
 .../prachtig-gitter_like.html                 |   0
 .../prachtig_gitter_like.html                 |   0
 Dockerfile                                    |   3 +-
 LICENSE.txt                                   |  21 ++
 Makefile                                      |   2 +
 compose.yml                                   |   2 +
 pyproject.toml                                |  24 +-
 setup.cfg                                     |   1 +
 src/snek/app.py                               |  79 +++--
 src/snek/form/__init__.py                     |   0
 src/snek/form/login.py                        |  24 ++
 src/snek/form/register.py                     |  31 ++
 src/snek/forms.py                             |  40 ---
 src/snek/gunicorn.py                          |   2 +-
 src/snek/model/__init__.py                    |   0
 src/snek/model/user.py                        |  19 ++
 src/snek/static/app.js                        |  15 +-
 src/snek/static/{styles.css => base.css}      |   9 -
 src/snek/static/fancy-button.js               |  54 +++
 src/snek/static/generic-form.css              | 100 ++++++
 src/snek/static/generic-form.js               | 321 ++++++++++++++++++
 .../static/{html_frame.css => html-frame.css} |   3 -
 .../static/{html_frame.js => html-frame.js}   |  15 +-
 .../static/{register.css => register__.css}   |  24 +-
 src/snek/system/__init__.py                   |   0
 src/snek/system/form.py                       | 171 ++++++++++
 src/snek/{ => system}/http.py                 |   0
 src/snek/{ => system}/middleware.py           |   0
 src/snek/{models.py => system/model.py}       |  87 ++---
 src/snek/templates/base.html                  |  31 ++
 src/snek/templates/base_chat.html             |  31 ++
 src/snek/templates/index.html                 |  20 ++
 src/snek/templates/login.html                 |  25 +-
 src/snek/templates/register.html              |  27 +-
 src/snek/templates/{test.html => web.html}    |   7 +-
 src/snek/view/base.py                         |  31 ++
 src/snek/view/index.py                        |   6 +
 src/snek/view/login.py                        |  13 +
 src/snek/view/login_form.py                   |   5 +
 src/snek/view/register.py                     |   6 +
 src/snek/view/register_form.py                |   5 +
 src/snek/view/view.py                         |   6 +
 42 files changed, 1050 insertions(+), 210 deletions(-)
 rename {src/snek/static => .resources}/prachtig-gitter_like.html (100%)
 rename {src/snek/templates => .resources}/prachtig_gitter_like.html (100%)
 create mode 100644 LICENSE.txt
 create mode 100644 src/snek/form/__init__.py
 create mode 100644 src/snek/form/login.py
 create mode 100644 src/snek/form/register.py
 delete mode 100644 src/snek/forms.py
 create mode 100644 src/snek/model/__init__.py
 create mode 100644 src/snek/model/user.py
 rename src/snek/static/{styles.css => base.css} (94%)
 create mode 100644 src/snek/static/fancy-button.js
 create mode 100644 src/snek/static/generic-form.css
 create mode 100644 src/snek/static/generic-form.js
 rename src/snek/static/{html_frame.css => html-frame.css} (54%)
 rename src/snek/static/{html_frame.js => html-frame.js} (65%)
 rename src/snek/static/{register.css => register__.css} (74%)
 create mode 100644 src/snek/system/__init__.py
 create mode 100644 src/snek/system/form.py
 rename src/snek/{ => system}/http.py (100%)
 rename src/snek/{ => system}/middleware.py (100%)
 rename src/snek/{models.py => system/model.py} (78%)
 create mode 100644 src/snek/templates/base.html
 create mode 100644 src/snek/templates/base_chat.html
 create mode 100644 src/snek/templates/index.html
 rename src/snek/templates/{test.html => web.html} (92%)
 create mode 100644 src/snek/view/base.py
 create mode 100644 src/snek/view/index.py
 create mode 100644 src/snek/view/login.py
 create mode 100644 src/snek/view/login_form.py
 create mode 100644 src/snek/view/register.py
 create mode 100644 src/snek/view/register_form.py
 create mode 100644 src/snek/view/view.py

diff --git a/src/snek/static/prachtig-gitter_like.html b/.resources/prachtig-gitter_like.html
similarity index 100%
rename from src/snek/static/prachtig-gitter_like.html
rename to .resources/prachtig-gitter_like.html
diff --git a/src/snek/templates/prachtig_gitter_like.html b/.resources/prachtig_gitter_like.html
similarity index 100%
rename from src/snek/templates/prachtig_gitter_like.html
rename to .resources/prachtig_gitter_like.html
diff --git a/Dockerfile b/Dockerfile
index 76436ba..47c0ece 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -37,4 +37,5 @@ RUN pip install --upgrade pip
 RUN pip install -e .
 EXPOSE 8081
 
-CMD ["gunicorn", "-w", "10", "-k", "aiohttp.worker.GunicornWebWorker", "snek.gunicorn:app","--bind","0.0.0.0:8081"]
+python -m snek.app
+#CMD ["gunicorn", "-w", "10", "-k", "aiohttp.worker.GunicornWebWorker", "snek.gunicorn:app","--bind","0.0.0.0:8081"]
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000..6b0da88
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2025 retoor
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/Makefile b/Makefile
index 65c58fa..f153fc1 100644
--- a/Makefile
+++ b/Makefile
@@ -10,6 +10,8 @@ install:
 	python3 -m venv .venv 
 	$(PIP) install -e .
 
+
+
 run:
 	$(GUNICORN) -w $(GUNICORN_WORKERS) -k aiohttp.worker.GunicornWebWorker snek.gunicorn:app --bind 0.0.0.0:$(PORT) --reload
 	
diff --git a/compose.yml b/compose.yml
index 776e60d..3b1f650 100644
--- a/compose.yml
+++ b/compose.yml
@@ -6,3 +6,5 @@ services:
       - "8081:8081"
     volumes:
       - ./:/code
+    entrypoint: ["python","-m","snek.app"]
+   
\ No newline at end of file
diff --git a/pyproject.toml b/pyproject.toml
index 07de284..d98557e 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,3 +1,25 @@
 [build-system]
 requires = ["setuptools", "wheel"]
-build-backend = "setuptools.build_meta"
\ No newline at end of file
+build-backend = "setuptools.build_meta"
+
+[project]
+name = "Snek"
+version = "1.0.0"
+readme = "README.md"
+license = { file = "LICENSE", content-type="text/markdown" }
+description = "Snek Chat Application by Molodetz"
+authors = [
+    { name = "retoor", email = "retoor@molodetz.nl" }
+]
+keywords = ["chat", "snek", "molodetz"]
+requires-python = ">=3.12"
+dependencies = [
+    "mkdocs>=1.4.0",
+    "shed",
+    "app @ git+https://retoor.molodetz.nl/retoor/app",
+    "beautifulsoup4",
+    "gunicorn",
+    "imgkit",
+    "wkhtmltopdf"
+]
+
diff --git a/setup.cfg b/setup.cfg
index ca8353d..045fc92 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -19,6 +19,7 @@ install_requires =
     gunicorn
     imgkit
     wkhtmltopdf
+    shed
 
 [options.packages.find]
 where = src
diff --git a/src/snek/app.py b/src/snek/app.py
index 97fbb96..0e2ed63 100644
--- a/src/snek/app.py
+++ b/src/snek/app.py
@@ -1,10 +1,16 @@
-from app.app import Application as BaseApplication
-from snek.forms import RegisterForm
-from aiohttp import web
-import aiohttp 
 import pathlib
-from snek import http 
-from snek.middleware import cors_allow_middleware,cors_middleware
+
+from aiohttp import web
+from app.app import Application as BaseApplication
+
+from snek.system import http
+from snek.system.middleware import cors_middleware
+from snek.view.index import IndexView
+from snek.view.login import LoginView
+from snek.view.login_form import LoginFormView
+from snek.view.register import RegisterView
+from snek.view.register_form import RegisterFormView
+from snek.view.view import WebView
 
 
 class Application(BaseApplication):
@@ -12,23 +18,38 @@ class Application(BaseApplication):
     def __init__(self, *args, **kwargs):
         middlewares = [
             cors_middleware,
-            web.normalize_path_middleware(merge_slashes=True)
+            web.normalize_path_middleware(merge_slashes=True),
         ]
         self.template_path = pathlib.Path(__file__).parent.joinpath("templates")
-        super().__init__(middlewares=middlewares, template_path=self.template_path,*args, **kwargs)
-        self.router.add_static("/",pathlib.Path(__file__).parent.joinpath("static"),name="static",show_index=True)
-        self.router.add_get("/register", self.handle_register)
-        self.router.add_get("/login", self.handle_login)
-        self.router.add_get("/test", self.handle_test)
-        self.router.add_post("/register", self.handle_register)
-        self.router.add_get("/http-get",self.handle_http_get)
-        self.router.add_get("/http-photo",self.handle_http_photo)
+        super().__init__(
+            middlewares=middlewares, template_path=self.template_path, *args, **kwargs
+        )
+        self.setup_router()
 
-    async def handle_test(self,request):
+    def setup_router(self):
+        self.router.add_get("/", IndexView)
+        self.router.add_static(
+            "/",
+            pathlib.Path(__file__).parent.joinpath("static"),
+            name="static",
+            show_index=True,
+        )
+        self.router.add_view("/web", WebView)
+        self.router.add_view("/login", LoginView)
+        self.router.add_view("/login-form", LoginFormView)
+        self.router.add_view("/register", RegisterView)
+        
+        self.router.add_view("/register-form", RegisterFormView)
+        self.router.add_get("/http-get", self.handle_http_get)
+        self.router.add_get("/http-photo", self.handle_http_photo)
 
-        return await self.render_template("test.html",request,context={"name":"retoor"})
+    async def handle_test(self, request):
 
-    async def handle_http_get(self, request:web.Request):
+        return await self.render_template(
+            "test.html", request, context={"name": "retoor"}
+        )
+
+    async def handle_http_get(self, request: web.Request):
         url = request.query.get("url")
         content = await http.get(url)
         return web.Response(body=content)
@@ -36,25 +57,13 @@ class Application(BaseApplication):
     async def handle_http_photo(self, request):
         url = request.query.get("url")
         path = await http.create_site_photo(url)
-        return web.Response(body=path.read_bytes(),headers={
-            "Content-Type": "image/png"
-        })
+        return web.Response(
+            body=path.read_bytes(), headers={"Content-Type": "image/png"}
+        )
 
-    async def handle_login(self, request):
-        if request.method == "GET":
-            return await self.render_template("login.html", request)    #web.json_response({"form": RegisterForm().to_json()})
-        elif request.method == "POST":
-            return await self.render_template("login.html", request)    #web.json_response({"form": RegisterForm().to_json()})
-        
-
-    async def handle_register(self, request):
-        if request.method == "GET":
-            return await self.render_template("register.html", request)    #web.json_response({"form": RegisterForm().to_json()})
-        elif request.method == "POST":
-            return self.render("register.html")
 
 app = Application()
 
-if __name__ == '__main__':
+if __name__ == "__main__":
 
-    web.run_app(app,port=8081,host="0.0.0.0")
+    web.run_app(app, port=8081, host="0.0.0.0")
diff --git a/src/snek/form/__init__.py b/src/snek/form/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/snek/form/login.py b/src/snek/form/login.py
new file mode 100644
index 0000000..a87f7d3
--- /dev/null
+++ b/src/snek/form/login.py
@@ -0,0 +1,24 @@
+from snek.system.form import Form, HTMLElement,FormInputElement,FormButtonElement
+
+class LoginForm(Form):
+
+    title = HTMLElement(tag="h1", text="Login")
+
+    username = FormInputElement(
+        name="username", 
+        required=True,
+        min_length=2,
+        max_length=20,
+        regex=r"^[a-zA-Z0-9_]+$",
+        place_holder="Username",
+        type="text"
+    )
+    password = FormInputElement(name="password",required=True,regex=r"^[a-zA-Z0-9_.+-]{6,}",type="password",place_holder="Password")
+
+    action = FormButtonElement(
+        name="action",
+        value="submit",
+        text="Login",
+        type="button"
+    )
+
diff --git a/src/snek/form/register.py b/src/snek/form/register.py
new file mode 100644
index 0000000..60399fb
--- /dev/null
+++ b/src/snek/form/register.py
@@ -0,0 +1,31 @@
+from snek.system.form import Form, HTMLElement,FormInputElement,FormButtonElement
+
+class RegisterForm(Form):
+
+    title = HTMLElement(tag="h1", text="Register")
+
+    username = FormInputElement(
+        name="username", 
+        required=True,
+        min_length=2,
+        max_length=20,
+        regex=r"^[a-zA-Z0-9_]+$",
+        place_holder="Username",
+        type="text"
+    )
+    email = FormInputElement(
+        name="email",
+        required=True,
+        regex=r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$",
+        place_holder="Email address",
+        type="email"
+    )
+    password = FormInputElement(name="password",required=True,regex=r"^[a-zA-Z0-9_.+-]{6,}",type="password",place_holder="Password")
+
+    action = FormButtonElement(
+        name="action",
+        value="submit",
+        text="Register",
+        type="button"
+    )
+
diff --git a/src/snek/forms.py b/src/snek/forms.py
deleted file mode 100644
index f4a7b24..0000000
--- a/src/snek/forms.py
+++ /dev/null
@@ -1,40 +0,0 @@
-from snek import models 
-
-class FormElement(models.ModelField):
-
-    def __init__(self,html_type, place_holder=None, *args, **kwargs):
-        super().__init__(*args, **kwargs)
-        self.place_holder = place_holder
-        self.html_type = html_type 
-
-    def to_json(self):
-        data = super().to_json()
-        data["html_type"] = self.html_type
-        data["place_holder"] = self.place_holder
-        return data 
-
-class Form(models.BaseModel):
-    pass
-
-class RegisterForm(Form):
-
-    username = FormElement(
-        name="username", 
-        required=True,
-        min_length=2,
-        max_length=20,
-        regex=r"^[a-zA-Z0-9_]+$",
-        place_holder="Username",
-        html_type="text"
-    )
-    email = FormElement(
-        name="email",
-        required=True,
-        regex=r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$",
-        place_holder="Email address",
-        html_type="email"
-    )
-    password = FormElement(name="password",required=True,regex=r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$",html_type="password")
-
-
-
diff --git a/src/snek/gunicorn.py b/src/snek/gunicorn.py
index 4055bbd..8583142 100644
--- a/src/snek/gunicorn.py
+++ b/src/snek/gunicorn.py
@@ -1,3 +1,3 @@
-from snek.app import app 
+from snek.app import app
 
 application = app
diff --git a/src/snek/model/__init__.py b/src/snek/model/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/snek/model/user.py b/src/snek/model/user.py
new file mode 100644
index 0000000..44553f8
--- /dev/null
+++ b/src/snek/model/user.py
@@ -0,0 +1,19 @@
+from snek.system.model import BaseModel,ModelField
+
+class User(BaseModel):
+    
+    username = ModelField(
+        name="username", 
+        required=True,
+        min_length=2,
+        max_length=20,
+        regex=r"^[a-zA-Z0-9_]+$",
+    )
+    email = ModelField(
+        name="email",
+        required=True,
+        regex=r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$"
+    )
+    password = ModelField(name="password",required=True,regex=r"^[a-zA-Z0-9_.+-]{6,}")
+
+
diff --git a/src/snek/static/app.js b/src/snek/static/app.js
index 701beda..f06ee24 100644
--- a/src/snek/static/app.js
+++ b/src/snek/static/app.js
@@ -57,13 +57,26 @@ class Room {
         this.name = name 
     }
     setMessages(list){
-        
+
     }
 
 
 }
 
 
+class InlineAppElement extends HTMLElement {
+    
+    constructor(){
+        this.
+    }
+
+}
+
+class Page {
+    elements = []
+
+}
+
 class App {
     rooms = []
     constructor() {
diff --git a/src/snek/static/styles.css b/src/snek/static/base.css
similarity index 94%
rename from src/snek/static/styles.css
rename to src/snek/static/base.css
index 83c1fb1..363e4f1 100644
--- a/src/snek/static/styles.css
+++ b/src/snek/static/base.css
@@ -1,11 +1,9 @@
-/* General Reset */
 * {
   margin: 0;
   padding: 0;
   box-sizing: border-box;
 }
 
-/* Body Styling */
 body {
   font-family: Arial, sans-serif;
   background-color: #1a1a1a;
@@ -16,7 +14,6 @@ body {
   height: 100vh;
 }
 
-/* Header Navigation */
 header {
   background-color: #0f0f0f;
   padding: 10px 20px;
@@ -43,14 +40,12 @@ header nav a:hover {
   color: #fff;
 }
 
-/* Main Layout */
 main {
   display: flex;
   flex: 1;
   overflow: hidden;
 }
 
-/* Sidebar */
 .sidebar {
   width: 250px;
   background-color: #121212;
@@ -84,7 +79,6 @@ main {
   color: #fff;
 }
 
-/* Chat Area */
 .chat-area {
   flex: 1;
   display: flex;
@@ -103,7 +97,6 @@ main {
   color: #fff;
 }
 
-/* Chat Messages */
 .chat-messages {
   flex: 1;
   padding: 20px;
@@ -155,7 +148,6 @@ main {
   color: #aaa;
 }
 
-/* Input Area */
 .chat-input {
   padding: 15px;
   background-color: #121212;
@@ -190,7 +182,6 @@ main {
   background-color: #e04924;
 }
 
-/* Responsive Adjustments */
 @media (max-width: 768px) {
   .sidebar {
     display: none;
diff --git a/src/snek/static/fancy-button.js b/src/snek/static/fancy-button.js
new file mode 100644
index 0000000..5407a3b
--- /dev/null
+++ b/src/snek/static/fancy-button.js
@@ -0,0 +1,54 @@
+
+
+class FancyButton extends HTMLElement {
+    url = null
+    type="button"
+    value = null
+    constructor(){
+        super()
+        this.attachShadow({mode:'open'})
+        this.container = document.createElement('span')
+        this.styleElement = document.createElement("style")
+        this.styleElement.innerHTML = `
+        :root {
+        width:100%;
+            --width: 100%;
+    }
+        button {
+            width: var(--width);
+            min-width: 33%;
+            padding: 10px;
+            background-color: #f05a28;
+            border: none;
+            border-radius: 5px;
+            color: white;
+            font-size: 1em;
+            font-weight: bold;
+            cursor: pointer;
+            transition: background-color 0.3s;
+    }
+        button:hover {
+        color: #EFEFEF;
+    background-color: #e04924;
+  }
+    `
+    this.container.appendChild(this.styleElement)
+        this.buttonElement = document.createElement('button')
+        this.container.appendChild(this.buttonElement)
+        this.shadowRoot.appendChild(this.container)
+    }
+
+    connectedCallback() {
+        this.url = this.getAttribute('url');
+        this.value = this.getAttribute('value')
+        const me = this 
+        this.buttonElement.appendChild(document.createTextNode(this.getAttribute("text")))
+        this.buttonElement.addEventListener("click",()=>{
+            if(me.url){
+                window.location = me.url
+            }
+        })
+    }
+}
+
+customElements.define("fancy-button",FancyButton)
diff --git a/src/snek/static/generic-form.css b/src/snek/static/generic-form.css
new file mode 100644
index 0000000..d593816
--- /dev/null
+++ b/src/snek/static/generic-form.css
@@ -0,0 +1,100 @@
+* {
+    margin: 0;
+    padding: 0;
+    box-sizing: border-box;
+  }
+  
+  body {
+    font-family: Arial, sans-serif;
+    background-color: #1a1a1a;
+    color: #e6e6e6;
+    line-height: 1.5;
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    align-items: center;
+    height: 100vh;
+  }
+
+generic-form {
+    margin: 0;
+    padding: 0;
+    box-sizing: border-box;
+    background-color: #000000;
+
+}
+
+.generic-form-container {
+    
+    background-color:  #0f0f0f;
+    border-radius: 10px;
+    padding: 30px;
+    width: 400px;
+    box-shadow: 0 0 15px rgba(0, 0, 0, 0.5);
+    text-align: center;
+
+}
+
+.generic-form-container h1 {
+    font-size: 2em;
+    color: #f05a28;
+    margin-bottom: 20px;
+}
+input {
+
+    border: 10px solid #000000;
+}
+.generic-form-container generic-field {
+    width: 100%;
+    padding: 10px;
+    margin: 10px 0;
+    border: 1px solid #333;
+    border-radius: 5px;
+    background-color: #1a1a1a;
+    color: #e6e6e6;
+    font-size: 1em;
+}
+
+.generic-form-container button {
+    width: 100%;
+    padding: 10px;
+    background-color: #f05a28;
+    border: none;
+    border-radius: 5px;
+    color: white;
+    font-size: 1em;
+    font-weight: bold;
+    cursor: pointer;
+    transition: background-color 0.3s;
+}
+
+.generic-form-container button:hover {
+    background-color: #e04924;
+}
+
+.generic-form-container a {
+    color: #f05a28;
+    text-decoration: none;
+    display: block;
+    margin-top: 15px;
+    font-size: 0.9em;
+    transition: color 0.3s;
+}
+
+.generic-form-container a:hover {
+    color: #e04924;
+}
+
+
+.error {
+    color: #d8000c;
+    font-size: 0.9em;
+    margin-top: 5px;
+}
+
+
+@media (max-width: 500px) {
+    .generic-form-container {
+        width: 90%;
+    }
+}
\ No newline at end of file
diff --git a/src/snek/static/generic-form.js b/src/snek/static/generic-form.js
new file mode 100644
index 0000000..11fea47
--- /dev/null
+++ b/src/snek/static/generic-form.js
@@ -0,0 +1,321 @@
+
+class GenericField extends HTMLElement {
+  form = null
+  field = null 
+  inputElement = null
+  footerElement = null 
+  action = null 
+  container = null
+  styleElement = null
+  name = null
+    get value() {
+      return this.inputElement.value
+    }
+    get type() {
+
+        return this.field.tag 
+    }
+    set value(val) {
+      val = val == null ? '' : val 
+      this.inputElement.value = val 
+      this.inputElement.setAttribute("value", val)
+    }
+    setInvalid(){
+      this.inputElement.classList.add("error")
+      this.inputElement.classList.remove("valid")
+    }
+    setErrors(errors){
+      if(errors.length)
+        this.inputElement.setAttribute("title", errors[0])
+      else
+       this.inputElement.setAttribute("title","")
+    }
+    setValid(){
+      this.inputElement.classList.remove("error")
+      this.inputElement.classList.add("valid")
+    }
+    constructor() {
+        super()
+        this.attachShadow({mode:'open'})
+        this.container = document.createElement('div')
+        this.styleElement = document.createElement('style')
+        this.styleElement.innerHTML = `
+
+            h1 {
+              font-size: 2em;
+              color: #f05a28;
+              margin-bottom: 20px;
+              margin-top: 0px;
+            }
+
+            input {
+                width: 90%;
+                padding: 10px;
+                margin: 10px 0;
+                border: 1px solid #333;
+                border-radius: 5px;
+                background-color: #1a1a1a;
+                color: #e6e6e6;
+                font-size: 1em;
+            }
+
+            button {
+                width: 50%;
+                padding: 10px;
+                background-color: #f05a28;
+                border: none;
+               float: right;
+                margin-top: 10px;
+                margin-left: 10px;
+                margin-right: 10px;
+                border-radius: 5px;
+                color: white;
+                font-size: 1em;
+                font-weight: bold;
+                cursor: pointer;
+                transition: background-color 0.3s;
+                clear: both;
+            }
+
+            button:hover {
+                background-color: #e04924;
+            }
+
+            a {
+                color: #f05a28;
+                text-decoration: none;
+                display: block;
+                margin-top: 15px;
+                font-size: 0.9em;
+                transition: color 0.3s;
+            }
+
+            a:hover {
+                color: #e04924;
+            }
+            .valid {
+                border: 1px solid green;
+                color:green;
+                font-size: 0.9em;
+                margin-top: 5px;
+            }
+            .error {
+              border: 3px solid red;
+                color: #d8000c;
+                font-size: 0.9em;
+                margin-top: 5px;
+            }
+            @media (max-width: 500px) {
+                input {
+                    width: 90%;
+                }
+            }    
+            
+            `
+        this.container.appendChild(this.styleElement)
+        
+        this.shadowRoot.appendChild(this.container)
+    }
+    connectedCallback(){
+
+      this.updateAttributes() 
+       
+    }
+    setAttribute(name,value){
+        this[name] = value 
+    }
+    updateAttributes(){
+      if(this.inputElement == null && this.field){
+       this.inputElement = document.createElement(this.field.tag)
+       if(this.field.tag == 'button'){
+          if(this.field.value == "submit"){
+          
+          
+          }
+          this.action = this.field.value
+       }
+       this.inputElement.name = this.field.name 
+       this.name = this.inputElement.name
+       const me = this
+       this.inputElement.addEventListener("keyup",(e)=>{
+        if(e.key == 'Enter'){
+            me.dispatchEvent(new Event("submit"))
+          }else if(me.field.value != e.target.value)
+          {
+            const event = new CustomEvent("change", {detail:me,bubbles:true})
+            me.dispatchEvent(event)
+          }
+       })
+       this.inputElement.addEventListener("click",(e)=>{
+          const event = new CustomEvent("click",{detail:me,bubbles:true})
+         me.dispatchEvent(event)    
+       })
+        this.container.appendChild(this.inputElement)
+
+}
+      if(!this.field){
+        return
+      }
+        this.inputElement.setAttribute("type",this.field.type == null ? 'input' : this.field.type)
+        this.inputElement.setAttribute("name",this.field.name  == null ? '' : this.field.name)
+        
+        if(this.field.text != null){
+          this.inputElement.innerText = this.field.text 
+        }
+        if(this.field.html != null){
+          this.inputElement.innerHTML = this.field.html
+        }
+        if(this.field.class_name){
+          this.inputElement.classList.add(this.field.class_name)
+        }
+        this.inputElement.setAttribute("tabindex", this.field.index)
+        this.inputElement.classList.add(this.field.name)
+        this.value = this.field.value
+        let place_holder = null 
+        if(this.field.place_holder)
+            place_holder = this.field.place_holder
+        if(this.field.required && place_holder){
+          place_holder = place_holder
+        }
+        if(place_holder)
+          this.field.place_holder = "* " + place_holder
+        this.inputElement.setAttribute("placeholder",place_holder)
+        if(this.field.required)
+            this.inputElement.setAttribute("required","required")
+        else
+            this.inputElement.removeAttribute("required")
+        if(!this.footerElement){
+            this.footerElement = document.createElement('div')
+            this.footerElement.style.clear = 'both'
+            this.container.appendChild(this.footerElement)
+        }
+    }
+}
+
+customElements.define('generic-field', GenericField);
+
+class GenericForm extends HTMLElement {
+  fields = {}  
+  form = {}
+  constructor() {
+
+
+      super();
+      this.attachShadow({ mode: 'open' });
+      this.styleElement = document.createElement("style")
+      this.styleElement.innerHTML = `
+
+      * {
+          margin: 0;
+          padding: 0;
+          box-sizing: border-box;
+          width:90%
+
+      }
+
+      div {
+          
+          background-color:  #0f0f0f;
+          border-radius: 10px;
+          padding: 30px;
+          width: 400px;
+          box-shadow: 0 0 15px rgba(0, 0, 0, 0.5);
+          text-align: center;
+
+      }
+      @media (max-width: 500px) {
+          form {
+              width: 80%;
+          }
+      }`
+      
+      this.container = document.createElement('div');
+      this.container.appendChild(this.styleElement)
+      this.container.classList.add("generic-form-container")
+      this.shadowRoot.appendChild(this.container);
+    }
+
+    connectedCallback() {
+      const url = this.getAttribute('url');
+      if (url) {
+        const fullUrl = url.startsWith("/") ? window.location.origin + url : new URL(window.location.origin + "/http-get")
+        if(!url.startsWith("/"))
+            fullUrl.searchParams.set('url', url)   
+        this.loadForm(fullUrl.toString());
+      } else {
+        this.container.textContent = "No URL provided!";
+      }
+    }
+
+    async loadForm(url) {
+      const me = this 
+      try {
+        const response = await fetch(url);
+        if (!response.ok) {
+          throw new Error(`Failed to fetch: ${response.status} ${response.statusText}`);
+        }
+        me.form = await response.json();
+        
+        let fields = Object.values(me.form.fields)
+        
+        fields = fields.sort((a,b)=>{
+          console.info(a.index,b.index)
+          return a.index - b.index
+        })  
+        fields.forEach(field=>{
+          const fieldElement = document.createElement('generic-field')
+          me.fields[field.name] = fieldElement 
+          fieldElement.setAttribute("form", me)
+          fieldElement.setAttribute("field", field)
+          me.container.appendChild(fieldElement)
+          fieldElement.updateAttributes() 
+          fieldElement.addEventListener("change",(e)=>{
+            me.form.fields[e.detail.name].value = e.detail.value
+          })
+          fieldElement.addEventListener("click",async (e)=>{
+            if(e.detail.type == "button"){
+              if(e.detail.value == "submit")
+              {
+                await me.validate()
+              }
+            }
+              
+          })
+        })
+        
+    } catch (error) {
+        this.container.textContent = `Error: ${error.message}`;
+      }
+    }
+    async validate(){
+      const url = this.getAttribute("url")
+      const me = this
+      const response = await fetch(url,{
+        method: 'POST',
+        headers: {
+          'Content-Type': 'application/json'
+        },
+        body: JSON.stringify({"action":"validate", "form":me.form})
+      });
+      const form = await response.json()
+      Object.values(form.fields).forEach(field=>{
+        if(!me.form.fields[field.name])
+          return
+          me.form.fields[field.name].is_valid = field.is_valid 
+        if(!field.is_valid){
+          me.fields[field.name].setInvalid()
+          me.fields[field.name].setErrors(field.errors)
+          console.info(field.name,"is invalid")
+        }else{
+          me.fields[field.name].setValid()
+        }
+        me.fields[field.name].setAttribute("field",field)
+        me.fields[field.name].updateAttributes()
+      })
+      Object.values(form.fields).forEach(field=>{
+        console.info(field.errors)
+        me.fields[field.name].setErrors(field.errors)
+      })
+    }
+  }
+  customElements.define('generic-form', GenericForm);
\ No newline at end of file
diff --git a/src/snek/static/html_frame.css b/src/snek/static/html-frame.css
similarity index 54%
rename from src/snek/static/html_frame.css
rename to src/snek/static/html-frame.css
index 6b64c76..92a1f97 100644
--- a/src/snek/static/html_frame.css
+++ b/src/snek/static/html-frame.css
@@ -1,9 +1,6 @@
 .html-frame {
     width: 100px;
     height: 50px;
-    position: relative;
     overflow: hidden;
-    clip-path: inset(0px 0px 50px 100px); /* Crop content */
     border: 1px solid black;
-
 }
\ No newline at end of file
diff --git a/src/snek/static/html_frame.js b/src/snek/static/html-frame.js
similarity index 65%
rename from src/snek/static/html_frame.js
rename to src/snek/static/html-frame.js
index 19a0c34..22581ce 100644
--- a/src/snek/static/html_frame.js
+++ b/src/snek/static/html-frame.js
@@ -12,28 +12,25 @@ class HTMLFrame extends HTMLElement {
       if (url) {
         const fullUrl = url.startsWith("/") ? window.location.origin + url : new URL(window.location.origin + "/http-get")
         if(!url.startsWith("/"))
-            fullUrl.searchParams.set('url', url)
-        console.info(fullUrl)    
-        this.fetchAndDisplayHtml(fullUrl.toString());
+            fullUrl.searchParams.set('url', url)   
+        this.loadAndRender(fullUrl.toString());
       } else {
-        this.container.textContent = "No URL provided!";
+        this.container.textContent = "No source URL!";
       }
     }
 
-    async fetchAndDisplayHtml(url) {
+    async loadAndRender(url) {
       try {
         const response = await fetch(url);
         if (!response.ok) {
-          throw new Error(`Failed to fetch: ${response.status} ${response.statusText}`);
+          throw new Error(`Error: ${response.status} ${response.statusText}`);
         }
         const html = await response.text();
-        this.container.innerHTML = html; // Insert the fetched HTML into the container
+        this.container.innerHTML = html;
         
     } catch (error) {
         this.container.textContent = `Error: ${error.message}`;
       }
     }
   }
-
-  // Define the custom element
   customElements.define('html-frame', HTMLFrame);
\ No newline at end of file
diff --git a/src/snek/static/register.css b/src/snek/static/register__.css
similarity index 74%
rename from src/snek/static/register.css
rename to src/snek/static/register__.css
index fc4ca0f..57186b8 100644
--- a/src/snek/static/register.css
+++ b/src/snek/static/register__.css
@@ -1,24 +1,12 @@
-/* General Reset */
+
 * {
     margin: 0;
     padding: 0;
     box-sizing: border-box;
   }
   
-  /* Body Styling */
-  body {
-    font-family: Arial, sans-serif;
-    background-color: #1a1a1a;
-    color: #e6e6e6;
-    line-height: 1.5;
-    display: flex;
-    flex-direction: column;
-    justify-content: center;
-    align-items: center;
-    height: 100vh;
-  }
   
-  /* Registration Form Container */
+  
   .registration-container {
     background-color: #0f0f0f;
     border-radius: 10px;
@@ -26,16 +14,15 @@
     width: 400px;
     box-shadow: 0 0 15px rgba(0, 0, 0, 0.5);
     text-align: center;
+    left: calc(50%-200);
   }
   
-  /* Form Heading */
   .registration-container h1 {
     font-size: 2em;
     color: #f05a28;
     margin-bottom: 20px;
   }
   
-  /* Input Fields */
   .registration-container input {
     width: 100%;
     padding: 10px;
@@ -47,7 +34,6 @@
     font-size: 1em;
   }
   
-  /* Submit Button */
   .registration-container button {
     width: 100%;
     padding: 10px;
@@ -65,7 +51,6 @@
     background-color: #e04924;
   }
   
-  /* Links */
   .registration-container a {
     color: #f05a28;
     text-decoration: none;
@@ -79,14 +64,11 @@
     color: #e04924;
   }
   
-  /* Error Message Styling */
   .error {
     color: #d8000c;
     font-size: 0.9em;
     margin-top: 5px;
   }
-  
-  /* Responsive Design */
   @media (max-width: 500px) {
     .registration-container {
       width: 90%;
diff --git a/src/snek/system/__init__.py b/src/snek/system/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/snek/system/form.py b/src/snek/system/form.py
new file mode 100644
index 0000000..68f7c0f
--- /dev/null
+++ b/src/snek/system/form.py
@@ -0,0 +1,171 @@
+from snek.system import model 
+
+class HTMLElement(model.ModelField):
+    def __init__(self,id:str=None, tag:str="div", name:str=None,html:str=None, class_name:str=None, text:str=None, *args, **kwargs):
+        """
+        Create a new HTMLElement.
+        
+        :param id: The id of the element
+        :param tag: The tag of the element
+        :param name: The name of the element, used to generate a class name if not provided
+        :param html: The inner html of the element
+        :param class_name: The class name of the element
+        :param text: The text of the element
+        """
+        self.tag = tag
+        self.text = text
+        self.id = id 
+        self.class_name = class_name or name
+        self.html = html 
+        super().__init__(name=name,*args, **kwargs)
+
+    def to_json(self):
+        """
+        Return a json representation of the element.
+        
+        This will return a dict with the following keys:
+        
+        - text: The text of the element
+        - id: The id of the element
+        - html: The inner html of the element
+        - class_name: The class name of the element
+        - tag: The tag of the element
+        
+        :return: A json representation of the element
+        :rtype: dict
+        """
+        result = super().to_json()
+        result['text'] = self.text 
+        result['id'] = self.id 
+        result['html'] = self.html 
+        result['class_name'] = self.class_name
+        result['tag'] = self.tag
+        return result 
+
+class FormElement(HTMLElement):
+    pass
+           
+class FormInputElement(FormElement):
+
+    def __init__(self,type="text",place_holder=None, *args, **kwargs):
+        """
+        Initialize a FormInputElement with specified attributes.
+
+        :param type: The type of the input element (default is "text").
+        :param place_holder: The placeholder text for the input element.
+        :param args: Additional positional arguments.
+        :param kwargs: Additional keyword arguments.
+        """
+
+        super().__init__(tag="input", *args, **kwargs)
+        self.place_holder = place_holder 
+        self.type = type
+        
+
+    def to_json(self):
+        """
+        Return a json representation of the element.
+
+        This will return a dict with the following keys:
+
+        - place_holder: The placeholder text for the input element
+        - type: The type of the input element
+
+        :return: A json representation of the element
+        :rtype: dict
+        """
+        data = super().to_json()
+        data["place_holder"] = self.place_holder
+        data["type"] = self.type
+        return data 
+    
+class FormButtonElement(FormElement):
+    # Just use the label text property to assign a button label.
+    def __init__(self, tag="button", *args, **kwargs):
+        """
+        Initialize a FormButtonElement with specified attributes.
+
+        :param tag: The tag of the button element (default is "button").
+        :param args: Additional positional arguments.
+        :param kwargs: Additional keyword arguments.
+        """
+        super().__init__(tag=tag, *args, **kwargs)
+
+
+class Form(model.BaseModel):
+    
+    @property
+    def html_elements(self):
+        """
+        Return a list of all :class:`HTMLElement` objects in the form.
+
+        This is a convenience property that filters the :attr:`fields` list to only
+        include elements that are instances of :class:`HTMLElement`.
+
+        :return: A list of :class:`HTMLElement` objects
+        :rtype: list
+        """
+        json_elements = super().to_json()
+        return [element for element in self.fields if isinstance(element,HTMLElement)]
+    def set_user_data(self, data):
+        """
+        Set user data for the form by updating the fields with the provided data.
+
+        This method extracts the 'fields' key from the provided data dictionary
+        and passes it to the parent class's `set_user_data` method to update the
+        form fields accordingly.
+
+        :param data: A dictionary containing the form data, expected to have a 
+                    'fields' key with the data to update the form fields.
+        """
+
+        return super().set_user_data(data.get('fields'))
+
+    def to_json(self, encode=False):
+        """
+        Return a JSON representation of the form, including field values and metadata.
+
+        This method returns a dictionary with the following keys:
+
+        - ``fields``: A dictionary of field names to their current values.
+        - ``is_valid``: A boolean indicating whether the form is valid.
+        - ``errors``: A dictionary of field names to lists of error strings.
+
+        If the ``encode`` argument is ``True``, the dictionary will be JSON-encoded
+        before being returned. Otherwise, the dictionary is returned directly.
+
+        :param encode: If ``True``, JSON-encode the returned dictionary.
+        :type encode: bool
+        :return: A JSON representation of the form.
+        :rtype: dict
+        """
+        elements = super().to_json()
+        html_elements = {}
+        for element in elements.keys():
+            print("DDD!",element,flush=True)
+            field = getattr(self,element)
+            if isinstance(field,HTMLElement):
+                print("QQQQ!",element,flush=True)
+                try:
+                    html_elements[element] = elements[element]
+                except KeyError:
+                    pass 
+
+        return dict(fields=html_elements,is_valid=self.is_valid,errors=self.errors)
+    @property
+    def errors(self):
+        """
+        Return a list of all error strings from all fields in the form.
+
+        The list will be empty if all fields are valid.
+
+        :return: A list of error strings.
+        :rtype: list
+        """
+        result = []
+        for field in self.html_elements:
+            result += field.errors 
+        return result 
+    @property
+    def is_valid(self):
+        return all(element.is_valid for element in self.html_elements)
diff --git a/src/snek/http.py b/src/snek/system/http.py
similarity index 100%
rename from src/snek/http.py
rename to src/snek/system/http.py
diff --git a/src/snek/middleware.py b/src/snek/system/middleware.py
similarity index 100%
rename from src/snek/middleware.py
rename to src/snek/system/middleware.py
diff --git a/src/snek/models.py b/src/snek/system/model.py
similarity index 78%
rename from src/snek/models.py
rename to src/snek/system/model.py
index ec5d9ce..a699a9e 100644
--- a/src/snek/models.py
+++ b/src/snek/system/model.py
@@ -23,7 +23,7 @@ def validate_attrs(required=False,min_length=None,max_length=None,regex=None,**k
             return add_attrs(required=required,min_length=min_length,max_length=max_length,regex=regex,**kwargs)(func)
 
 class Validator:
-
+    _index = 0
     @property
     def value(self):
         return self._value 
@@ -34,12 +34,14 @@ class Validator:
 
     @property
     def initial_value(self):
-        return None
+        return self.value
 
     def custom_validation(self):
         return True
 
     def __init__(self,required=False,min_num=None,max_num=None,min_length=None,max_length=None,regex=None,value=None,kind=None,help_text=None,**kwargs):
+        self.index = Validator._index
+        Validator._index += 1
         self.required = required 
         self.min_num = min_num 
         self.max_num = max_num
@@ -47,8 +49,10 @@ class Validator:
         self.max_length = max_length 
         self.regex = regex 
         self._value = None 
-        self.value = value 
-        self.type = kind
+        self.value = value
+        print("xxxx", value,flush=True) 
+        
+        self.kind = kind
         self.help_text = help_text 
         self.__dict__.update(kwargs)
     @property 
@@ -61,7 +65,7 @@ class Validator:
         if self.value is None:
             return error_list 
 
-        if self.type == float or self.type == int:
+        if self.kind == float or self.kind == int:
             if self.min_num is not None and self.value < self.min_num:
                 error_list.append("Field should be minimal {}.".format(self.min_num))
             if self.max_num is not None and self.value > self.max_num:
@@ -70,10 +74,11 @@ class Validator:
             error_list.append("Field should be minimal {} characters long.".format(self.min_length))
         if self.max_length is not None and len(self.value) > self.max_length:
             error_list.append("Field should be maximal {} characters long.".format(self.max_length))
+        print(self.regex, self.value,flush=True)
         if not self.regex is None and not self.value is None and not re.match(self.regex, self.value):
             error_list.append("Invalid value.".format(self.regex))
-        if not self.type is None and type(self.value) != self.type:
-            error_list.append("Invalid type. It is supposed to be {}.".format(self.type))
+        if not self.kind is None and type(self.value) != self.kind:
+            error_list.append("Invalid kind. It is supposed to be {}.".format(self.kind))
         return error_list 
         
     def validate(self):
@@ -89,6 +94,8 @@ class Validator:
         except ValueError:
             return False
 
+    
+
     def to_json(self):
         return {
             "required": self.required,
@@ -98,19 +105,26 @@ class Validator:
             "max_length": self.max_length,
             "regex": self.regex,
             "value": self.value,
-            "type": self.type,
+            "kind": str(self.kind),
             "help_text": self.help_text,
             "errors": self.errors,
-            "is_valid": self.is_valid
+            "is_valid": self.is_valid,
+            "index":self.index
         }
 
 class ModelField(Validator):
+
+    index = 1
     def __init__(self,name=None,save=True, *args, **kwargs):
         self.name = name 
-        
         self.save = save
         super().__init__(*args, **kwargs)
 
+    def to_json(self):
+        result = super().to_json()
+        result['name'] = self.name
+        return result 
+
 
 class CreatedField(ModelField):
     
@@ -146,9 +160,11 @@ class BaseModel:
     updated_at = UpdatedField(name="updated_at",regex=TIMESTAMP_REGEX,place_holder="Updated at")
     deleted_at = DeletedField(name="deleted_at",regex=TIMESTAMP_REGEX, place_holder="Deleted at")
 
+   
     def __init__(self, *args, **kwargs):
         print(self.__dict__)
         print(dir(self.__class__))
+        self.fields = {}
         for key in dir(self.__class__):
             obj = getattr(self.__class__,key)
 
@@ -156,6 +172,7 @@ class BaseModel:
                 self.__dict__[key] = copy.deepcopy(obj)
                 print("JAAA")
                 self.__dict__[key].value = kwargs.pop(key,self.__dict__[key].initial_value)
+                self.fields[key] = self.__dict__[key]
 
     def __setitem__(self, key, value):
         obj = self.__dict__.get(key)
@@ -169,6 +186,22 @@ class BaseModel:
             return obj.value 
         return obj
 
+    def set_user_data(self, data):
+        for key, value in data.items():
+            field = self.fields.get(key)
+            if not field:
+                continue 
+            if value.get('name'):
+                value = value.get('value')
+            field.value = value
+           
+
+    @property 
+    def is_valid(self):
+        for field in self.fields.values():
+            if not field.is_valid:
+                return False
+        return True
 
     def __getitem__(self, key):
         obj = self.__dict__.get(key)
@@ -180,7 +213,7 @@ class BaseModel:
         if isinstance(obj,Validator):
             obj.value = value
         else:
-            setattr(self,key,value)
+            self.__dict__[key] = value #setattr(self,key,value)
     #def __getattr__(self, key):
     #    obj = self.__dict__.get(key)
     #    if isinstance(obj,Validator):
@@ -201,6 +234,7 @@ class BaseModel:
             "updated_at": self.updated_at.value,
             "deleted_at": self.deleted_at.value
         })
+        
         for key,value in self.__dict__.items(): 
             if key == "record":
                 continue
@@ -225,39 +259,8 @@ class FormElement(ModelField):
         self.place_holder = place_holder 
         super().__init__(*args, **kwargs)
 
-
     def to_json(self):
         data = super().to_json()
         data["name"] = self.name 
         data["place_holder"] = self.place_holder
         return data 
-
-
-
-
-class TestModel(BaseModel):
-
-    first_name = FormElement(name="first_name",required=True,min_length=3,max_length=20,regex=r"^[a-zA-Z0-9_]+$",place_holder="First name")
-    last_name = FormElement(name="last_name",required=True,min_length=3,max_length=20,regex=r"^[a-zA-Z0-9_]+$",place_holder="Last name")
-    email = FormElement(name="email",required=True,regex=r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$",place_holder="Email address")  
-    password = FormElement(name="password",required=True,regex=r"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*#?&])[A-Za-z\d@$!%*#?&]{8,}$",place_holder="Password")
-
-class Form:
-    username = FormElement(required=True,min_length=3,max_length=20,regex=r"^[a-zA-Z0-9_]+$",place_holder="Username")
-    email = FormElement(required=True,regex=r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$",place_holder="Email address")
-    def __init__(self, *args, **kwargs):
-        self.place_holder = kwargs.pop("place_holder",None) 
-
-
-if __name__ == "__main__":
-    model = TestModel(first_name="John",last_name="Doe",email="n9K9p@example.com",password="Password123")
-    model2 = TestModel(first_name="John",last_name="Doe",email="ddd",password="zzz")
-    model.first_name = "AAA"
-    print(model.first_name)
-    print(model.first_name.value)
-    
-    print(model.first_name)
-    print(model.first_name.value)
-    print(model.to_json(True))
-    print(model2.to_json(True))
-    print(model2.record)
diff --git a/src/snek/templates/base.html b/src/snek/templates/base.html
new file mode 100644
index 0000000..5b1bdf2
--- /dev/null
+++ b/src/snek/templates/base.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <title>{% block title %}{% endblock %}</title>
+   <script src="/fancy-button.js"></script>
+    <link rel="stylesheet" href="/style.css">
+    <link rel="stylesheet" href="/generic-form.css">
+    <script src="/html-frame.js"></script>
+    <script src="/generic-form.js"></script>
+    <link rel="stylesheet" href="/html-frame.css"></script>
+  
+</head>
+<body>
+    <header>
+        {% block header %}
+        {% endblock %}
+
+    </header>
+    <main>
+        <aside class="sidebar">
+        {% block sidebar %}
+        
+        {% endblock %}
+    </aside>
+    {% block main %}
+    {% endblock %}
+</main>
+</body>
+</html>
\ No newline at end of file
diff --git a/src/snek/templates/base_chat.html b/src/snek/templates/base_chat.html
new file mode 100644
index 0000000..09c025b
--- /dev/null
+++ b/src/snek/templates/base_chat.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <title>{% block title %}{% endblock %}</title>
+   <script src="/fancy-button.js"></script>
+    <link rel="stylesheet" href="/base.css">
+    <link rel="stylesheet" href="/generic-form.css">
+    <script src="/html-frame.js"></script>
+    <script src="/generic-form.js"></script>
+    <link rel="stylesheet" href="/html-frame.css"></script>
+    <link rel="stylesheet" href="/register__.css">
+</head>
+<body>
+    <header>
+        {% block header %}
+        {% endblock %}
+
+    </header>
+    <main>
+        <aside class="sidebar">
+            {% block sidebar %}
+            
+            {% endblock %}
+        </aside>
+    {% block main %}
+    {% endblock %}
+</main>
+</body>
+</html>
\ No newline at end of file
diff --git a/src/snek/templates/index.html b/src/snek/templates/index.html
new file mode 100644
index 0000000..1ee1f77
--- /dev/null
+++ b/src/snek/templates/index.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <title>Snek chat by Molodetz</title>
+    <link rel="stylesheet" href="generic-form.css">
+  <link rel="stylesheet" href="register__.css">
+    <script src="/fancy-button.js"></script>
+</head>
+<body>
+  <div class="registration-container">
+    <h1>Snek</h1>
+    <fancy-button url="/login" text="Login"></fancy-button>
+    <span style="padding:10px;">Or</span>
+    <fancy-button url="/register" text="Register"></fancy-button>
+
+  </div>
+</body>
+</html>
diff --git a/src/snek/templates/login.html b/src/snek/templates/login.html
index d37f3fa..c09ec70 100644
--- a/src/snek/templates/login.html
+++ b/src/snek/templates/login.html
@@ -1,20 +1,5 @@
-<!DOCTYPE html>
-<html lang="en">
-<head>
-  <meta charset="UTF-8">
-  <meta name="viewport" content="width=device-width, initial-scale=1.0">
-  <title>Register</title>
-  <link rel="stylesheet" href="register.css">
-</head>
-<body>
-  <div class="registration-container">
-    <h1>Login</h1>
-    <form>
-      <input type="text" name="username" placeholder="Username or password" required>
-      <input type="password" name="password" placeholder="Password" required>
-      <button type="submit">Create Account</button>
-      <a href="/register">Not having an account yet? Register here.</a>
-    </form>
-  </div>
-</body>
-</html>
+{% extends "base.html" %}
+
+{% block main %}
+  <generic-form url="/login-form"></generic-form>
+{% endblock %}
diff --git a/src/snek/templates/register.html b/src/snek/templates/register.html
index da41629..61da961 100644
--- a/src/snek/templates/register.html
+++ b/src/snek/templates/register.html
@@ -1,22 +1,5 @@
-<!DOCTYPE html>
-<html lang="en">
-<head>
-  <meta charset="UTF-8">
-  <meta name="viewport" content="width=device-width, initial-scale=1.0">
-  <title>Register</title>
-  <link rel="stylesheet" href="register.css">
-</head>
-<body>
-  <div class="registration-container">
-    <h1>Register</h1>
-    <form>
-      <input type="text" name="username" placeholder="Username" required>
-      <input type="email" name="email" placeholder="Email Address" required>
-      <input type="password" name="password" placeholder="Password" required>
-      <input type="password" name="confirm_password" placeholder="Confirm Password" required>
-      <button type="submit">Create Account</button>
-      <a href="#">Already have an account? Login here.</a>
-    </form>
-  </div>
-</body>
-</html>
+{% extends "base.html" %}
+
+{% block main %}
+  <generic-form url="/register-form"></generic-form>
+{% endblock %}
\ No newline at end of file
diff --git a/src/snek/templates/test.html b/src/snek/templates/web.html
similarity index 92%
rename from src/snek/templates/test.html
rename to src/snek/templates/web.html
index c23103a..0403e1b 100644
--- a/src/snek/templates/test.html
+++ b/src/snek/templates/web.html
@@ -4,9 +4,7 @@
   <meta charset="UTF-8">
   <meta name="viewport" content="width=device-width, initial-scale=1.0">
   <title>Dark Themed Chat Application</title>
-  <link rel="stylesheet" href="styles.css">
-  <script src="/html_frame.js"></script>
-  <script src="/html_frame.css"></script>
+  <link rel="stylesheet" href="base.css">
 </head>
 <body>
   <header>
@@ -59,5 +57,4 @@
   </main>
   
 </body>
-</html>
-
+</html>
\ No newline at end of file
diff --git a/src/snek/view/base.py b/src/snek/view/base.py
new file mode 100644
index 0000000..d962ee5
--- /dev/null
+++ b/src/snek/view/base.py
@@ -0,0 +1,31 @@
+from aiohttp import web 
+
+class BaseView(web.View):
+    
+    @property 
+    def app(self):
+        return self.request.app
+    
+    @property
+    def db(self):
+        return self.app.db
+
+    def json_response(self, data):
+        return web.json_response(data)
+
+    def render_template(self, template_name, context=None):
+        return self.request.app.render_template(template_name, self.request,context)
+    
+class BaseFormView(BaseView):
+
+    form = None 
+
+    async def get(self):
+        form = self.form()
+        return self.json_response(form.to_json())
+       
+    async def post(self):
+        form = self.form()
+        post = await self.request.json()
+        form.set_user_data(post['form'])
+        return self.json_response(form.to_json())  
\ No newline at end of file
diff --git a/src/snek/view/index.py b/src/snek/view/index.py
new file mode 100644
index 0000000..a5d8b92
--- /dev/null
+++ b/src/snek/view/index.py
@@ -0,0 +1,6 @@
+from snek.view.base import BaseView
+
+class IndexView(BaseView):
+
+    async def get(self):
+        return await self.render_template("index.html")
diff --git a/src/snek/view/login.py b/src/snek/view/login.py
new file mode 100644
index 0000000..3a3beaf
--- /dev/null
+++ b/src/snek/view/login.py
@@ -0,0 +1,13 @@
+from snek.form.register import RegisterForm
+from snek.view.base import BaseView 
+
+class LoginView(BaseView):
+
+    async def get(self):
+        return await self.render_template("login.html")    #web.json_response({"form": RegisterForm().to_json()})
+        
+    async def post(self):
+        form = RegisterForm()
+        form.set_user_data(await self.request.post())
+        print(form.is_valid())
+        return await self.render_template("login.html", self.request)    #web.json_response({"form": RegisterForm().to_json()})
diff --git a/src/snek/view/login_form.py b/src/snek/view/login_form.py
new file mode 100644
index 0000000..26527da
--- /dev/null
+++ b/src/snek/view/login_form.py
@@ -0,0 +1,5 @@
+from snek.view.base import BaseFormView
+from snek.form.login import LoginForm
+
+class LoginFormView(BaseFormView):
+    form = LoginForm
\ No newline at end of file
diff --git a/src/snek/view/register.py b/src/snek/view/register.py
new file mode 100644
index 0000000..095b7a3
--- /dev/null
+++ b/src/snek/view/register.py
@@ -0,0 +1,6 @@
+from snek.view.base import BaseView 
+
+class RegisterView(BaseView):
+
+    async def get(self):
+        return await self.render_template("register.html")  
\ No newline at end of file
diff --git a/src/snek/view/register_form.py b/src/snek/view/register_form.py
new file mode 100644
index 0000000..0ae7630
--- /dev/null
+++ b/src/snek/view/register_form.py
@@ -0,0 +1,5 @@
+from snek.form.register import RegisterForm
+from snek.view.base import BaseFormView 
+
+class RegisterFormView(BaseFormView):
+    form = RegisterForm
\ No newline at end of file
diff --git a/src/snek/view/view.py b/src/snek/view/view.py
new file mode 100644
index 0000000..ea642a3
--- /dev/null
+++ b/src/snek/view/view.py
@@ -0,0 +1,6 @@
+from snek.view.base import BaseView 
+
+class WebView(BaseView):
+
+    async def get(self):
+        return await self.render_template("web.html")
\ No newline at end of file