Merge branch 'master' into ViewReport

This commit is contained in:
Jack 2019-02-08 17:31:36 -08:00
commit 4ed0b26811
20 changed files with 348 additions and 137 deletions

View file

@ -9,8 +9,9 @@ verify_ssl = true
django = "==2.1.5" django = "==2.1.5"
django-cors-headers = "==2.4.0" django-cors-headers = "==2.4.0"
djangorestframework = "==3.8.2" djangorestframework = "==3.8.2"
gunicorn = "==19.6.0"
django-rest-auth = "==0.9.3" django-rest-auth = "==0.9.3"
django-allauth = "==0.37.1"
gunicorn = "==19.6.0"
[requires] [requires]
python_version = "3.5" python_version = "3.5"

72
back/Pipfile.lock generated
View file

@ -1,7 +1,7 @@
{ {
"_meta": { "_meta": {
"hash": { "hash": {
"sha256": "d3bf402a934e168cbdc04022effcdb9ff8d4fde5b83d79bb388ad2a4c547894a" "sha256": "b1fc6b06ec8daa4efd9573865bc6c1732ae9354309e036bfe3ce0ab76b1a3bcd"
}, },
"pipfile-spec": 6, "pipfile-spec": 6,
"requires": { "requires": {
@ -16,6 +16,27 @@
] ]
}, },
"default": { "default": {
"certifi": {
"hashes": [
"sha256:47f9c83ef4c0c621eaef743f133f09fa8a74a9b75f037e8624f83bd1b6626cb7",
"sha256:993f830721089fef441cdfeb4b2c8c9df86f0c63239f06bd025a76a7daddb033"
],
"version": "==2018.11.29"
},
"chardet": {
"hashes": [
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
],
"version": "==3.0.4"
},
"defusedxml": {
"hashes": [
"sha256:24d7f2f94f7f3cb6061acb215685e5125fbcdc40a857eff9de22518820b0a4f4",
"sha256:702a91ade2968a82beb0db1e0766a6a273f33d4616a6ce8cde475d8e09853b20"
],
"version": "==0.5.0"
},
"django": { "django": {
"hashes": [ "hashes": [
"sha256:a32c22af23634e1d11425574dce756098e015a165be02e4690179889b207c7a8", "sha256:a32c22af23634e1d11425574dce756098e015a165be02e4690179889b207c7a8",
@ -24,6 +45,13 @@
"index": "pypi", "index": "pypi",
"version": "==2.1.5" "version": "==2.1.5"
}, },
"django-allauth": {
"hashes": [
"sha256:02175aa1c2ddfd935a54011d1196d70c976647fc46f603f8b8758fc395b9d277"
],
"index": "pypi",
"version": "==0.37.1"
},
"django-cors-headers": { "django-cors-headers": {
"hashes": [ "hashes": [
"sha256:5545009c9b233ea7e70da7dbab7cb1c12afa01279895086f98ec243d7eab46fa", "sha256:5545009c9b233ea7e70da7dbab7cb1c12afa01279895086f98ec243d7eab46fa",
@ -55,6 +83,27 @@
"index": "pypi", "index": "pypi",
"version": "==19.6.0" "version": "==19.6.0"
}, },
"idna": {
"hashes": [
"sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407",
"sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c"
],
"version": "==2.8"
},
"oauthlib": {
"hashes": [
"sha256:0ce32c5d989a1827e3f1148f98b9085ed2370fc939bf524c9c851d8714797298",
"sha256:3e1e14f6cde7e5475128d30e97edc3bfb4dc857cb884d8714ec161fdbb3b358e"
],
"version": "==3.0.1"
},
"python3-openid": {
"hashes": [
"sha256:0086da6b6ef3161cfe50fb1ee5cceaf2cda1700019fda03c2c5c440ca6abe4fa",
"sha256:628d365d687e12da12d02c6691170f4451db28d6d68d050007e4a40065868502"
],
"version": "==3.1.0"
},
"pytz": { "pytz": {
"hashes": [ "hashes": [
"sha256:32b0891edff07e28efe91284ed9c31e123d84bea3fd98e1f72be2508f43ef8d9", "sha256:32b0891edff07e28efe91284ed9c31e123d84bea3fd98e1f72be2508f43ef8d9",
@ -62,12 +111,33 @@
], ],
"version": "==2018.9" "version": "==2018.9"
}, },
"requests": {
"hashes": [
"sha256:502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e",
"sha256:7bf2a778576d825600030a110f3c0e3e8edc51dfaafe1c146e39a2027784957b"
],
"version": "==2.21.0"
},
"requests-oauthlib": {
"hashes": [
"sha256:bd6533330e8748e94bf0b214775fed487d309b8b8fe823dc45641ebcd9a32f57",
"sha256:d3ed0c8f2e3bbc6b344fa63d6f933745ab394469da38db16bdddb461c7e25140"
],
"version": "==1.2.0"
},
"six": { "six": {
"hashes": [ "hashes": [
"sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c",
"sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73"
], ],
"version": "==1.12.0" "version": "==1.12.0"
},
"urllib3": {
"hashes": [
"sha256:61bf29cada3fc2fbefad4fdf059ea4bd1b4a86d2b6d15e1c7c0b582b9752fe39",
"sha256:de9529817c93f27c8ccbfead6985011db27bd0ddfcdb2d86f3f663385c6a9c22"
],
"version": "==1.24.1"
} }
}, },
"develop": {} "develop": {}

View file

@ -0,0 +1,18 @@
# Generated by Django 2.1.5 on 2019-02-07 22:02
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('backend', '0004_auto_20190131_1645'),
]
operations = [
migrations.AddField(
model_name='field',
name='field_name',
field=models.CharField(default='field', max_length=512),
),
]

View file

@ -26,12 +26,13 @@ class Section(models.Model):
class Field(models.Model): class Field(models.Model):
section_id = models.ForeignKey(Section, on_delete=models.CASCADE) section_id = models.ForeignKey(Section, on_delete=models.CASCADE)
field_name = models.CharField(max_length=512, default="field")
label = models.CharField(max_length=512) label = models.CharField(max_length=512)
number = models.IntegerField() number = models.IntegerField()
type = models.CharField(max_length=128) type = models.CharField(max_length=128)
completed = models.BooleanField(default=False) completed = models.BooleanField(default=False)
data_bool = models.BooleanField(default=False) data_bool = models.BooleanField(default=False)
data_decimal = models.DecimalField(max_digits=9,decimal_places=2, null=True, blank=True) data_decimal = models.DecimalField(max_digits=9, decimal_places=2, null=True, blank=True)
data_date = models.DateField(default=datetime.date.today) data_date = models.DateField(default=datetime.date.today)
data_file = models.FileField(upload_to='uploads/%Y/%m/%d/', max_length=512, null=True, blank=True) data_file = models.FileField(upload_to='uploads/%Y/%m/%d/', max_length=512, null=True, blank=True)
data_string = models.TextField(default='', blank=True) data_string = models.TextField(default='', blank=True)

View file

@ -1,10 +1,37 @@
# simple_policy.py
from datetime import date from datetime import date
from policy import Policy, Section
# - For the rules, should one refer to fields by 'section.fields.x' #### Classes for policy, sections.
# or by the section name eg. 'general_section.fields.x'?
class Policy():
def __init__(self):
self.sections = []
def add_section(self, section):
self.sections.append(section)
class Section():
def __init__(self, title="Section", html_description="", required=False,
auto_submit=False, fields={}):
self.title = title
self.html_description = html_description
self.required = required
self.auto_submit = auto_submit
self.fields = fields
self.rules = []
def add_rule(self, title="Rule", rule=None, rule_break_text=""):
rule = {
"title": title,
"rule": rule,
"rule_break_text": rule_break_text,
}
self.rules.append(rule)
#### Policy configuration begin here
pol = Policy()
#### General #### General
#### Section 0 #### Section 0
@ -12,7 +39,7 @@ general_section = Section(
title="General Info", title="General Info",
html_description="", html_description="",
fields={ fields={
"destination": {"label": "Destination City", "type": "string"} "destination": {"label": "Destination City", "type": "string"},
} }
) )
@ -22,7 +49,7 @@ general_section.add_rule(
rule_break_text="What did the cowboy say about Tim, his wild horse?" rule_break_text="What did the cowboy say about Tim, his wild horse?"
) )
Policy.add_section(general_section) pol.add_section(general_section)
#### Flight #### Flight
#### Section 1 #### Section 1
@ -34,6 +61,7 @@ flight_section = Section(
"departure_date": {"label": "Departure date", "type": "date"}, "departure_date": {"label": "Departure date", "type": "date"},
"return_date": {"label": "Return date", "type": "date"}, "return_date": {"label": "Return date", "type": "date"},
"fare": {"label": "Fare", "type": "decimal"}, "fare": {"label": "Fare", "type": "decimal"},
"layovers": {"label": "Transit wait", "type": "integer"},
} }
) )
@ -43,13 +71,14 @@ flight_section.add_rule(
rule_break_text="Fares cannot be more than $500" rule_break_text="Fares cannot be more than $500"
) )
Policy.add_section(flight_section) pol.add_section(flight_section)
#### Lodging #### Lodging
#### Section 2 #### Section 2
lodging_section = Section( lodging_section = Section(
title="Hotel Info", title="Hotel Info",
html_description="<p>Enter hotel info here.\nPer diem rates can be found at <a href='https://www.gsa.gov/travel/plan-book/per-diem-rates'></a></p>", html_description="<p>Enter hotel info here.\nPer diem rates can be found at "
"<a href='https://www.gsa.gov/travel/plan-book/per-diem-rates'></a></p>",
fields={ fields={
"check-in_date": {"label": "Check-in date", "type": "date"}, "check-in_date": {"label": "Check-in date", "type": "date"},
"check-out_date": {"label": "Check-out date", "type": "date"}, "check-out_date": {"label": "Check-out date", "type": "date"},
@ -64,13 +93,13 @@ def nightly_rate_check(report, section):
duration = checkout_date - checkin_date duration = checkout_date - checkin_date
return section.fields.cost <= duration * section.fields.rate return section.fields.cost <= duration * section.fields.rate
section.add_rule( lodging_section.add_rule(
title="", title="",
rule=nightly_rate_check, rule=nightly_rate_check,
rule_break_text="The average nightly rate cannot be more than the USGSA rate." rule_break_text="The average nightly rate cannot be more than the USGSA rate."
) )
Policy.add_section(lodging_section) pol.add_section(lodging_section)
#### Local Transportation #### Local Transportation
#### Section 3 #### Section 3
@ -89,7 +118,7 @@ transport_section.add_rule(
rule_break_text="Local transportation costs must be less than $10 per day, on average." rule_break_text="Local transportation costs must be less than $10 per day, on average."
) )
Policy.add_section(transport_section) pol.add_section(transport_section)
#### Per Diem #### Per Diem
#### Section 4 #### Section 4
@ -109,24 +138,4 @@ per_diem_section.add_rule(
rule_break_text="The average cost per day for per diem expenses cannot be more than the rate specified by the USGSA." rule_break_text="The average cost per day for per diem expenses cannot be more than the rate specified by the USGSA."
) )
Policy.add_section(per_diem_section) pol.add_section(per_diem_section)
'''
Section(
title="",
html_description="<p></p>",
fields={
"": {"label": "", "type": ""}
}
)
section.add_rule(
title="",
rule=lambda report, section: boolean_statement,
rule_break_text=""
)
#// or, for a rule which doesnt apply to a specific section...
#//
#// add_general_rule(...)
'''

3
back/backend/test.py Normal file
View file

@ -0,0 +1,3 @@
from policy import pol
print(pol)

View file

@ -1,6 +1,7 @@
from rest_framework.decorators import api_view from rest_framework.decorators import api_view
from django.http import JsonResponse from django.http import JsonResponse
from .models import * from .models import *
from .policy import pol
# function that prints all the reports # function that prints all the reports
@ -47,15 +48,17 @@ def get_sections(r_id):
def get_fields(s_id): def get_fields(s_id):
# create dict of arrays for fields # create dict of arrays for fields
field_set = {"fields": []} field_set = {"fields": []}
queryset = Field.objects.filter(section_id=s_id) queryset = Field.objects.filter(section_id=s_id).order_by('number')
# queryset = Field.objects.all()
for i in queryset: for i in queryset:
# function to print corresponding datatype
value = get_datatype(i)
data = { data = {
"field_name": "TODO", "field_name": i.field_name,
"label": i.label, "label": i.label,
"type": i.type, "type": i.type,
"number": i.number, "number": i.number,
"value": "get_value", "value": value
} }
# append the fields to array # append the fields to array
# use copy() to avoid overwriting # use copy() to avoid overwriting
@ -63,6 +66,29 @@ def get_fields(s_id):
return field_set return field_set
# function to convert value into JSON
def to_json(convert):
return {"value": convert}
# function that gets corresponding
# data type
def get_datatype(self):
if self.type == "boolean":
if self.data_bool:
return True
else:
return False
elif self.type == "decimal":
return self.data_decimal
elif self.type == "date":
return "{}".format(self.data_date)
elif self.type == "file":
return "{}".format(self.data_file)
elif self.type == "string":
return "{}".format(self.data_string)
elif self.type == "integer":
return self.data_integer
# API Endpoints # API Endpoints
@api_view(['POST']) @api_view(['POST'])
@ -70,75 +96,37 @@ def report(request):
''' '''
Generate a new empty report and return it Generate a new empty report and return it
''' '''
data = {
"title": "2018 Portland trip", # Create the report
"date_created": "2018-05-22T14:56:28.000Z", report = Report.objects.create(user_id=request.user, title=request.data['title'], date_created=datetime.date.today())
"submitted": False, report.save()
"date_submitted": "0000-00-00T00:00:00.000Z",
"sections": [ # Create the sections
{ for i in range(len(pol.sections)):
"id": 1, section = pol.sections[i]
"completed": True, s = Section.objects.create(report_id=report, auto_submit=section.auto_submit, required=section.required, completed=False, title=section.title, html_description=section.html_description, number=i)
"title": "Flight Info", s.save()
"html_description": "<p>Enter flight details here.</p>",
"fields": { # Create the fields
"international": { j = 0
"label": "International flight", for key in section.fields:
"type": "boolean", field = section.fields[key]
"value": True f = Field.objects.create(section_id=s, field_name=key, label=field['label'], number=j, type=field['type'], completed=False)
}, f.save()
"travel_date": { j = j+1
"label": "Travel start date",
"type": "date", # Return the newly created report
"value": "2016-05-22T14:56:28.000Z" data = get_reports(report.id)
},
"fare": {
"label": "Fare",
"type": "decimal",
"value": "1024.99"
},
"lowest_fare_screenshot": {
"label": "Lowest fare screenshot",
"type": "file",
"value": "e92h842jiu49f8..."
},
"plane_ticket_invoice": {
"label": "Plane ticket invoice PDF",
"type": "file",
"value": ""
}
},
"rule_violations": [
{
"error_text": "Plane ticket invoice must be submitted."
}
]
},
{
"id": 2,
"completed": False,
"title": "Hotel info",
"html_description": "<p>If you used a hotel, please enter the details.</p>",
"fields": {
"total": {
"label": "Total cost",
"type": "decimal"
}
},
"rule_violations": [
]
}
]
}
return JsonResponse(data) return JsonResponse(data)
# List of reports # List of reports
@api_view(['GET']) @api_view(['GET'])
def reports(request): def reports(request):
report_set = {"reports": []} report_set = {"reports": []}
queryset = Report.objects.all() queryset = Report.objects.all().filter(user_id=request.user.id).order_by('date_created')
for i in queryset: for i in queryset:
data = { data = {
"user_id": request.user.id,
"report_pk": i.id, "report_pk": i.id,
"title": i.title, "title": i.title,
"date_created": i.date_created, "date_created": i.date_created,

Binary file not shown.

View file

@ -39,10 +39,15 @@ INSTALLED_APPS = [
'django.contrib.sessions', 'django.contrib.sessions',
'django.contrib.messages', 'django.contrib.messages',
'django.contrib.staticfiles', 'django.contrib.staticfiles',
'django.contrib.sites',
# 3rd party # 3rd party
'rest_framework', 'rest_framework',
'rest_framework.authtoken', 'rest_framework.authtoken',
'allauth',
'allauth.account',
'allauth.socialaccount',
'rest_auth', 'rest_auth',
'rest_auth.registration',
'corsheaders', 'corsheaders',
# local # local
'users', 'users',
@ -149,3 +154,25 @@ USE_TZ = True
# https://docs.djangoproject.com/en/2.1/howto/static-files/ # https://docs.djangoproject.com/en/2.1/howto/static-files/
STATIC_URL = '/static/' STATIC_URL = '/static/'
# Email Config
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
SITE_ID = 1
# Registration
#ACCOUNT_USER_MODEL_USERNAME_FIELD = 'email'
ACCOUNT_EMAIL_REQUIRED = True
ACCOUNT_USERNAME_REQUIRED = False
ACCOUNT_AUTHENTICATION_METHOD = 'email'
REST_AUTH_REGISTER_SERIALIZERS = {
'REGISTER_SERIALIZER': 'users.serializers.RegisterSerializer',
}
AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend',
'allauth.account.auth_backends.AuthenticationBackend',
)

View file

@ -13,5 +13,7 @@ urlpatterns = [
path('admin/', admin.site.urls), path('admin/', admin.site.urls),
path('api/v1/', include("backend.urls")), path('api/v1/', include("backend.urls")),
path('api/v1/account/', include('rest_auth.urls')), path('api/v1/account/', include('rest_auth.urls')),
path('api/v1/account/register/', include('rest_auth.registration.urls')),
# path('api/v1/account/register/', NameRegistrationView.as_view()),
path('api-auth/', include('rest_framework.urls')), path('api-auth/', include('rest_framework.urls')),
] ]

47
back/users/serializers.py Normal file
View file

@ -0,0 +1,47 @@
from rest_framework import serializers
from allauth.account import app_settings as allauth_settings
from allauth.utils import email_address_exists
from allauth.account.adapter import get_adapter
from allauth.account.utils import setup_user_email
from django.utils.translation import gettext as _
class RegisterSerializer(serializers.Serializer):
email = serializers.EmailField(required=allauth_settings.EMAIL_REQUIRED)
first_name = serializers.CharField(required=True, write_only=True)
last_name = serializers.CharField(required=True, write_only=True)
password1 = serializers.CharField(required=True, write_only=True)
password2 = serializers.CharField(required=True, write_only=True)
def validate_email(self, email):
email = get_adapter().clean_email(email)
if allauth_settings.UNIQUE_EMAIL:
if email and email_address_exists(email):
raise serializers.ValidationError(
_("A user is already registered with this e-mail address."))
return email
def validate_password1(self, password):
return get_adapter().clean_password(password)
def validate(self, data):
if data['password1'] != data['password2']:
raise serializers.ValidationError(
_("The two password fields didn't match."))
return data
def get_cleaned_data(self):
return {
'first_name': self.validated_data.get('first_name', ''),
'last_name': self.validated_data.get('last_name', ''),
'password1': self.validated_data.get('password1', ''),
'email': self.validated_data.get('email', ''),
}
def save(self, request):
adapter = get_adapter()
user = adapter.new_user(request)
self.cleaned_data = self.get_cleaned_data()
adapter.save_user(request, user, self)
setup_user_email(request, user, [])
user.save()
return user

View file

@ -1,3 +1 @@
from django.shortcuts import render from django.shortcuts import render
# Create your views here.

View file

@ -36,7 +36,7 @@
</ul> </ul>
</div> </div>
</nav> </nav>
<div class="container pt-5"> <div class="container pt-3">
<div class="row"> <div class="row">
<div class="col-sm-8 mx-auto"> <div class="col-sm-8 mx-auto">
<div class="card bg-light text-dark"> <div class="card bg-light text-dark">

View file

@ -35,7 +35,7 @@
</ul> </ul>
</div> </div>
</nav> </nav>
<div class="container"> <div class="container pt-3">
<p>Welcome to Reimbursinator</p> <p>Welcome to Reimbursinator</p>
</div> </div>
<script src="js/logout.js"></script> <script src="js/logout.js"></script>

View file

@ -27,7 +27,7 @@
</ul> </ul>
</div> </div>
</nav> </nav>
<div class="container"> <div class="container pt-3">
<div class="jumbotron"> <div class="jumbotron">
<h1>Reimbursinator</h1> <h1>Reimbursinator</h1>
<p class="lead">An open source expense management solution sponsored by the Software Freedom Conservancy</p> <p class="lead">An open source expense management solution sponsored by the Software Freedom Conservancy</p>

View file

@ -2,7 +2,7 @@ function postToLoginEndpoint(event) {
event.preventDefault(); event.preventDefault();
const credentials = { const credentials = {
"username" : this.elements.username.value, "email" : this.elements.email.value,
"password" : this.elements.password.value "password" : this.elements.password.value
} }
const url = "https://" + window.location.hostname + ":8444/api/v1/account/login/"; const url = "https://" + window.location.hostname + ":8444/api/v1/account/login/";

View file

@ -1,20 +1,64 @@
const password = document.getElementById("password"); const password1 = document.getElementById("password1");
const confirm_password = document.getElementById("confirmPassword"); const password2 = document.getElementById("password2");
function validatePassword(){
if(password.value != confirm_password.value) { function validatePassword() {
confirm_password.setCustomValidity("Passwords Don't Match"); if (password1.value != password2.value) {
} password2.setCustomValidity("Passwords don't match");
else { } else {
confirm_password.setCustomValidity(''); password2.setCustomValidity('');
} }
} }
password.onchange = validatePassword; password1.onchange = validatePassword;
confirm_password.onkeyup = validatePassword; password2.onkeyup = validatePassword;
function validateEmail(email) function validateEmail(email) {
{ if (email.validity.patternMismatch) {
if(email.validity.patternMismatch)
email.setCustomValidity('Please input correct email'); email.setCustomValidity('Please input correct email');
else } else {
email.setCustomValidity(''); email.setCustomValidity('');
} }
}
function postToRegistrationEndpoint(event) {
event.preventDefault();
const credentials = {
"email" : this.elements.email.value,
"first_name" : this.elements.first_name.value,
"last_name" : this.elements.last_name.value,
"password1" : this.elements.password1.value,
"password2" : this.elements.password2.value
}
const url = "https://" + window.location.hostname + ":8444/api/v1/account/register/";
const xhr = new XMLHttpRequest();
console.log("Attempting a connection to the following endpoint: " + url);
console.log("User credentials:\n" + JSON.stringify(credentials));
xhr.open("POST", url, true);
xhr.setRequestHeader("Content-Type", "application/json");
xhr.onreadystatechange = function() {
if (this.readyState === 4) {
if (this.status === 201) {
console.log("REGISTRATION SUCCESS!");
console.log("Server response:\n" + this.response);
token = JSON.parse(this.response).key;
localStorage.setItem("token", token);
window.location.replace("home.html");
} else {
console.error("REGISTRATION FAILURE!");
console.error("Server status: " + this.status);
console.error("Server response:\n" + this.response);
}
}
};
xhr.onerror = function() {
alert("Error connecting to the authentication server!");
};
xhr.send(JSON.stringify(credentials));
}
const form = document.querySelector("form");
form.addEventListener("submit", postToRegistrationEndpoint);

View file

@ -14,7 +14,7 @@
<nav class="navbar navbar-expand-sm navbar-dark bg-primary"> <nav class="navbar navbar-expand-sm navbar-dark bg-primary">
<div class="navbar-brand">Reimbursinator</div> <div class="navbar-brand">Reimbursinator</div>
</nav> </nav>
<div class="container pt-5"> <div class="container pt-3">
<div class="row"> <div class="row">
<div class="col-sm-6 mx-auto"> <div class="col-sm-6 mx-auto">
<div class="card bg-light text-dark"> <div class="card bg-light text-dark">
@ -24,12 +24,12 @@
<div class="card-body"> <div class="card-body">
<form class="form" autocomplete="off"> <form class="form" autocomplete="off">
<div class="form-group"> <div class="form-group">
<label for="formGroupUsername">Username:</label> <label for="email">Email:</label>
<input class="form-control" id="formGroupUsername" type="text" name="username" required autofocus> <input class="form-control" id="email" type="email" name="email" required>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="formGroupPassword">Password:</label> <label for="password">Password:</label>
<input class="form-control" id="formGroupPassword" type="password" name="password" required> <input class="form-control" id="password" type="password" name="password" required>
</div> </div>
<p id="errorLogin" style="color:red"></p> <p id="errorLogin" style="color:red"></p>
<button type="submit" class="btn btn-primary pull-right">Submit</button> <button type="submit" class="btn btn-primary pull-right">Submit</button>

View file

@ -35,7 +35,7 @@
</ul> </ul>
</div> </div>
</nav> </nav>
<div class="container"> <div class="container pt-3">
<p>Create a new report</p> <p>Create a new report</p>
</div> </div>
<script src="js/logout.js"></script> <script src="js/logout.js"></script>

View file

@ -14,7 +14,7 @@
<nav class="navbar navbar-expand-sm navbar-dark bg-primary"> <nav class="navbar navbar-expand-sm navbar-dark bg-primary">
<div class="navbar-brand">Reimbursinator</div> <div class="navbar-brand">Reimbursinator</div>
</nav> </nav>
<div class="container pt-5"> <div class="container pt-3">
<div class="row"> <div class="row">
<div class="col-sm-6 mx-auto"> <div class="col-sm-6 mx-auto">
<div class="card bg-light text-dark"> <div class="card bg-light text-dark">
@ -23,21 +23,25 @@
</div> </div>
<div class="card-body"> <div class="card-body">
<form class="form signup" autocomplete="off" action="index.html"> <form class="form signup" autocomplete="off" action="index.html">
<div class="form-group">
<label for="userName">Username:</label>
<input class="form-control" id="userName" type="text" name="username" minlength="4" size="10" required autofocus>
</div>
<div class="form-group"> <div class="form-group">
<label for="email">Email:</label> <label for="email">Email:</label>
<input class="form-control" type="email" id="email" pattern="[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,3}$" oninput="validateEmail(this);" required> <input class="form-control" type="email" id="email" pattern="[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,3}$" oninput="validateEmail(this);" required>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="password">Password:</label> <label for="first_name">First Name:</label>
<input class="form-control" type="password" id="password" minlength="4" size="10" required> <input class="form-control" id="first_name" type="text" name="first_name" required>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="confirmPassword">Confirm Password:</label> <label for="last_name">Last Name:</label>
<input class="form-control" type="password" id="confirmPassword" required> <input class="form-control" id="last_name" type="text" name="last_name" required>
</div>
<div class="form-group">
<label for="password1">Password:</label>
<input class="form-control" type="password" id="password1" name="password1" minlength="8" required>
</div>
<div class="form-group">
<label for="password2">Confirm Password:</label>
<input class="form-control" type="password" id="password2" name="password2" minlength="8" required>
</div> </div>
<button type="submit" class="btn btn-primary">Submit</button> <button type="submit" class="btn btn-primary">Submit</button>
</form> </form>
@ -51,5 +55,4 @@
</div> </div>
</body> </body>
<script src="js/signupPage.js"></script> <script src="js/signupPage.js"></script>
<!--Still need to check if user exist and if email exist test-->
</html> </html>