28 | {% block content %}
29 | {% endblock %}
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/shop/templates/shop/checkout.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 | {% load get_image_url %}
3 |
4 | {% block title %}
5 | {% if name %}
6 | {{ name|title }}
7 | {% else %}
8 | Online Shopping for Electronics, Apparel, Computers, Books, DVDs & more
9 | {% endif %}
10 | {% endblock %}
11 |
12 | {% block meta %}
13 |
14 | {% endblock %}
15 |
16 | {% block content %}
17 |
18 |
Checkout Page
19 |
20 |
21 | Hey thank you for purchasing on Amazon.com. Your items will be delivered to your address soon!
22 |
23 |
24 |
25 |
26 | Continue Shopping
27 |
28 |
29 | {% endblock %}
--------------------------------------------------------------------------------
/shop/models.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 | from ckeditor.fields import RichTextField
3 |
4 | # Create your models here.
5 | class Category(models.Model):
6 | name = models.CharField(max_length=50)
7 | slug = models.SlugField(max_length=200, unique=True)
8 |
9 | class Meta:
10 | ordering = ('name', )
11 | verbose_name = 'category'
12 | verbose_name_plural = 'catergories'
13 |
14 | def __str__(self):
15 | return self.name
16 |
17 | class Product(models.Model):
18 | name = models.CharField(max_length=200)
19 | img = models.ImageField(upload_to='products/images', blank=True)
20 | price = models.DecimalField(max_digits=6, decimal_places=2)
21 | stock = models.IntegerField(default=0)
22 | description = models.TextField()
23 | slug = models.SlugField(max_length=200, unique=True)
24 | manufactured_on = models.DateTimeField()
25 | category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name='products')
26 | features = RichTextField(blank=True)
27 |
28 | class Meta:
29 | ordering = ('name', )
30 | verbose_name = 'product'
31 | verbose_name_plural = 'products'
32 |
33 | def __str__(self):
34 | return self.name
--------------------------------------------------------------------------------
/cart/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.1.1 on 2020-10-03 12:46
2 |
3 | from django.conf import settings
4 | from django.db import migrations, models
5 | import django.db.models.deletion
6 |
7 |
8 | class Migration(migrations.Migration):
9 |
10 | initial = True
11 |
12 | dependencies = [
13 | ('shop', '0004_auto_20200930_1049'),
14 | migrations.swappable_dependency(settings.AUTH_USER_MODEL),
15 | ]
16 |
17 | operations = [
18 | migrations.CreateModel(
19 | name='Cart',
20 | fields=[
21 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
22 | ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
23 | ],
24 | ),
25 | migrations.CreateModel(
26 | name='CartItem',
27 | fields=[
28 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
29 | ('price', models.DecimalField(decimal_places=2, max_digits=10)),
30 | ('quantity', models.IntegerField(default=0)),
31 | ('cart', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cart.cart')),
32 | ('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='shop.product')),
33 | ],
34 | ),
35 | ]
36 |
--------------------------------------------------------------------------------
/shop/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.1.1 on 2020-09-26 14:43
2 |
3 | from django.db import migrations, models
4 | import django.db.models.deletion
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | initial = True
10 |
11 | dependencies = [
12 | ]
13 |
14 | operations = [
15 | migrations.CreateModel(
16 | name='Category',
17 | fields=[
18 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
19 | ('name', models.CharField(max_length=50)),
20 | ('slug', models.SlugField(max_length=200, unique=True)),
21 | ],
22 | ),
23 | migrations.CreateModel(
24 | name='Product',
25 | fields=[
26 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
27 | ('name', models.CharField(max_length=60)),
28 | ('img', models.ImageField(blank=True, upload_to='products/images')),
29 | ('price', models.DecimalField(decimal_places=2, max_digits=6)),
30 | ('description', models.TextField()),
31 | ('slug', models.SlugField(max_length=200, unique=True)),
32 | ('available', models.BooleanField(default=True)),
33 | ('manufactured_on', models.DateTimeField()),
34 | ('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='products', to='shop.category')),
35 | ],
36 | ),
37 | ]
38 |
--------------------------------------------------------------------------------
/shop/forms.py:
--------------------------------------------------------------------------------
1 | from django.contrib.auth.models import User
2 | from django import forms
3 |
4 | class SignupForm(forms.Form):
5 | your_name = forms.CharField(label='Your name', max_length=100)
6 | email = forms.EmailField(label='Email')
7 | password = forms.CharField(label='Password', widget=forms.PasswordInput)
8 | re_enter_password = forms.CharField(label='Re-enter password', widget=forms.PasswordInput)
9 |
10 | def clean_your_name(self):
11 | your_name = self.cleaned_data.get('your_name')
12 | user = User.objects.filter(username=your_name)
13 |
14 | if user:
15 | raise forms.ValidationError("Username already exists!")
16 |
17 | return your_name
18 |
19 | def clean_email(self):
20 | email = self.cleaned_data.get('email')
21 | user = User.objects.filter(email=email)
22 |
23 | if user:
24 | raise forms.ValidationError("Email already taken!")
25 |
26 | return email
27 |
28 | def clean_re_enter_password(self):
29 | password = self.cleaned_data.get('password')
30 | re_enter_password = self.cleaned_data.get('re_enter_password')
31 |
32 | if not re_enter_password == password:
33 | raise forms.ValidationError("Password Doesn't match!")
34 |
35 | return re_enter_password
36 |
37 | def save(self, commit=True):
38 | username = self.cleaned_data.get('your_name')
39 | email = self.cleaned_data.get('email')
40 | password = self.cleaned_data.get('password')
41 |
42 | user = User.objects.create_user(
43 | username=username,
44 | email=email,
45 | password=password
46 | )
47 |
48 | return user
49 |
50 | class SignInForm(forms.Form):
51 | email = forms.EmailField(label='Email')
52 | password = forms.CharField(label='Password', widget=forms.PasswordInput)
--------------------------------------------------------------------------------
/shop/templates/shop/index.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 | {% load get_image_url %}
3 |
4 | {% block title %}
5 | {% if name %}
6 | {{ name|title }}
7 | {% else %}
8 | Online Shopping for Electronics, Apparel, Computers, Books, DVDs & more
9 | {% endif %}
10 | {% endblock %}
11 |
12 | {% block meta %}
13 |
14 | {% endblock %}
15 |
16 | {% block content %}
17 |
18 |
RESULTS
19 | {% if products %}
20 |
21 | {% for product in products %}
22 |
23 |
24 |

25 |
26 |
44 |
45 | {% endfor %}
46 |
47 | {% else %}
48 |
No Products Found!
49 | {% endif %}
50 |
51 | {% endblock %}
--------------------------------------------------------------------------------
/shop/templates/auth/signin.html:
--------------------------------------------------------------------------------
1 | {% extends 'base_auth.html' %}
2 | {% load static %}
3 |
4 | {% block title %}
5 | SignIn
6 | {% endblock %}
7 |
8 | {% block meta %}
9 |
10 | {% endblock %}
11 |
12 | {% block content %}
13 |
62 | {% endblock %}z
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | share/python-wheels/
24 | *.egg-info/
25 | .installed.cfg
26 | *.egg
27 | MANIFEST
28 |
29 | # PyInstaller
30 | # Usually these files are written by a python script from a template
31 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
32 | *.manifest
33 | *.spec
34 |
35 | # Installer logs
36 | pip-log.txt
37 | pip-delete-this-directory.txt
38 |
39 | # Unit test / coverage reports
40 | htmlcov/
41 | .tox/
42 | .nox/
43 | .coverage
44 | .coverage.*
45 | .cache
46 | nosetests.xml
47 | coverage.xml
48 | *.cover
49 | *.py,cover
50 | .hypothesis/
51 | .pytest_cache/
52 | cover/
53 |
54 | # Translations
55 | *.mo
56 | *.pot
57 |
58 | # Django stuff:
59 | *.log
60 | local_settings.py
61 | db.sqlite3
62 | db.sqlite3-journal
63 |
64 | # Flask stuff:
65 | instance/
66 | .webassets-cache
67 |
68 | # Scrapy stuff:
69 | .scrapy
70 |
71 | # Sphinx documentation
72 | docs/_build/
73 |
74 | # PyBuilder
75 | .pybuilder/
76 | target/
77 |
78 | # Jupyter Notebook
79 | .ipynb_checkpoints
80 |
81 | # IPython
82 | profile_default/
83 | ipython_config.py
84 |
85 | # pyenv
86 | # For a library or package, you might want to ignore these files since the code is
87 | # intended to run in multiple environments; otherwise, check them in:
88 | # .python-version
89 |
90 | # pipenv
91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
94 | # install all needed dependencies.
95 | #Pipfile.lock
96 |
97 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow
98 | __pypackages__/
99 |
100 | # Celery stuff
101 | celerybeat-schedule
102 | celerybeat.pid
103 |
104 | # SageMath parsed files
105 | *.sage.py
106 |
107 | # Environments
108 | .env
109 | .venv
110 | env/
111 | venv/
112 | ENV/
113 | env.bak/
114 | venv.bak/
115 |
116 | # Spyder project settings
117 | .spyderproject
118 | .spyproject
119 |
120 | # Rope project settings
121 | .ropeproject
122 |
123 | # mkdocs documentation
124 | /site
125 |
126 | # mypy
127 | .mypy_cache/
128 | .dmypy.json
129 | dmypy.json
130 |
131 | # Pyre type checker
132 | .pyre/
133 |
134 | # pytype static type analyzer
135 | .pytype/
136 |
137 | # Cython debug symbols
138 | cython_debug/
--------------------------------------------------------------------------------
/shop/templates/shop/detail.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% load get_image_url %}
3 |
4 | {% block title %}
5 | {{ product.name }}
6 | {% endblock %}
7 |
8 | {% block meta %}
9 |
10 | {% endblock %}
11 |
12 | {% block content %}
13 |
14 |
17 |
18 |
19 |

20 |
21 |
22 |
23 | {{ product.name }}
24 |
25 |
26 |
Price: {{ product.price }}
27 |
41 |
42 |
43 |
Details
44 |
45 | Mfg: {{ product.manufactured_on }}
46 |
47 |
48 |
49 | {% if product.features %}
50 |
51 |
About this item
52 | {{ product.features|safe }}
53 |
54 | {% endif %}
55 |
56 |
57 |
58 |
59 | {% if similar_items %}
60 |
61 | Similar Items
62 |
63 |
64 | {% for item in similar_items %}
65 |
66 |

67 |
68 | {{ item.name }}
69 |
70 |
71 | {{ item.price }}
72 |
73 | {% if item.stock > 0 %}
74 |
75 | {% else %}
76 |
77 | {% endif %}
78 |
79 | {% endfor %}
80 |
81 |
82 | {% endif %}
83 |
84 |
85 |
86 | Description
87 | {{ product.description }}
88 |
89 |
90 | {% endblock %}
--------------------------------------------------------------------------------
/shop/templates/auth/signup.html:
--------------------------------------------------------------------------------
1 | {% extends 'base_auth.html' %}
2 | {% load static %}
3 |
4 | {% block title %}
5 | Signup
6 | {% endblock %}
7 |
8 | {% block meta %}
9 |
10 | {% endblock %}
11 |
12 | {% block content %}
13 |
90 | {% endblock %}
--------------------------------------------------------------------------------
/amazon/settings.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 | import os
3 | import django_heroku
4 |
5 | # Build paths inside the project like this: BASE_DIR / 'subdir'.
6 | BASE_DIR = Path(__file__).resolve().parent.parent
7 |
8 | # Quick-start development settings - unsuitable for production
9 | # See https://docs.djangoproject.com/en/3.1/howto/deployment/checklist/
10 |
11 | # SECURITY WARNING: keep the secret key used in production secret!
12 | SECRET_KEY = os.environ.get('SECRET_KEY')
13 | # SECURITY WARNING: don't run with debug turned on in production!
14 | DEBUG = (os.environ.get('DEBUG_VALUE') == 'True')
15 |
16 | ALLOWED_HOSTS = ['amazondjangoclone.herokuapp.com']
17 |
18 |
19 | # Application definition
20 |
21 | INSTALLED_APPS = [
22 | 'django.contrib.admin',
23 | 'django.contrib.auth',
24 | 'django.contrib.contenttypes',
25 | 'django.contrib.sessions',
26 | 'django.contrib.messages',
27 | 'django.contrib.staticfiles',
28 | 'shop.apps.ShopConfig',
29 | 'ckeditor',
30 | 'cart.apps.CartConfig',
31 | ]
32 |
33 | MIDDLEWARE = [
34 | 'django.middleware.security.SecurityMiddleware',
35 | 'django.contrib.sessions.middleware.SessionMiddleware',
36 | 'django.middleware.common.CommonMiddleware',
37 | 'django.middleware.csrf.CsrfViewMiddleware',
38 | 'django.contrib.auth.middleware.AuthenticationMiddleware',
39 | 'django.contrib.messages.middleware.MessageMiddleware',
40 | 'django.middleware.clickjacking.XFrameOptionsMiddleware',
41 | 'shop.request_exposer.RequestExposerMiddleware'
42 | ]
43 |
44 | ROOT_URLCONF = 'amazon.urls'
45 |
46 | TEMPLATES = [
47 | {
48 | 'BACKEND': 'django.template.backends.django.DjangoTemplates',
49 | 'DIRS': [],
50 | 'APP_DIRS': True,
51 | 'OPTIONS': {
52 | 'context_processors': [
53 | 'django.template.context_processors.debug',
54 | 'django.template.context_processors.request',
55 | 'django.contrib.auth.context_processors.auth',
56 | 'django.contrib.messages.context_processors.messages',
57 | ],
58 | },
59 | },
60 | ]
61 |
62 | WSGI_APPLICATION = 'amazon.wsgi.application'
63 |
64 |
65 | # Database
66 | # https://docs.djangoproject.com/en/3.1/ref/settings/#databases
67 |
68 | DATABASES = {
69 | 'default': {
70 | 'ENGINE': 'django.db.backends.sqlite3',
71 | 'NAME': BASE_DIR / 'db.sqlite3',
72 | }
73 | }
74 |
75 |
76 | # Password validation
77 | # https://docs.djangoproject.com/en/3.1/ref/settings/#auth-password-validators
78 |
79 | AUTH_PASSWORD_VALIDATORS = [
80 | {
81 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
82 | },
83 | {
84 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
85 | },
86 | {
87 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
88 | },
89 | {
90 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
91 | },
92 | ]
93 |
94 |
95 | # Internationalization
96 | # https://docs.djangoproject.com/en/3.1/topics/i18n/
97 |
98 | LANGUAGE_CODE = 'en-us'
99 |
100 | TIME_ZONE = 'UTC'
101 |
102 | USE_I18N = True
103 |
104 | USE_L10N = True
105 |
106 | USE_TZ = True
107 |
108 |
109 | # Static files (CSS, JavaScript, Images)
110 | # https://docs.djangoproject.com/en/3.1/howto/static-files/
111 |
112 | STATIC_URL = '/static/'
113 | STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
114 |
115 | MEDIA_URL = '/media/'
116 | MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
117 |
118 | CKEDITOR_CONFIGS = {
119 | 'default': {
120 | 'toolbar': None,
121 | },
122 | }
123 |
124 | CART_SESSION_ID = 'cart'
125 |
126 | LOGIN_URL = "shop:signin"
127 |
128 | django_heroku.settings(locals())
--------------------------------------------------------------------------------
/shop/templates/shop/shopping_cart.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 | {% load get_image_url %}
3 |
4 | {% block title %}
5 | {% if name %}
6 | {{ name|title }}
7 | {% else %}
8 | Online Shopping for Electronics, Apparel, Computers, Books, DVDs & more
9 | {% endif %}
10 | {% endblock %}
11 |
12 | {% block meta %}
13 |
14 | {% endblock %}
15 |
16 | {% block content %}
17 |
18 |
Shopping Cart
19 | {% if products %}
20 |
21 | {% for item in products %}
22 |
23 |
24 |

25 |
26 |
27 |
28 | {{ item.product.name }}
29 |
30 |
31 | {% if item.product.stock > 0 %}
32 | In Stock
33 | {% else %}
34 | Out Of Stock
35 | {% endif %}
36 |
37 |
38 |
39 |
Qty:
40 |
41 | {% if item.product.stock > 0 %}
42 |
43 | {% else %}
44 |
45 | {% endif %}
46 |
47 |
{{ item.quantity }}
48 |
49 | {% if item.quantity == 1 %}
50 |
51 | {% else %}
52 |
53 | {% endif %}
54 |
55 |
56 |
57 | |
58 |
59 |
60 | Delete
61 |
62 |
63 |
64 |
65 |
66 | {{ item.price }}
67 |
68 |
69 |
70 | {% endfor %}
71 |
72 | Subtotal():
73 |
74 |
77 |
78 | {% else %}
79 |
Your Cart is Empty!
80 | {% endif %}
81 |
82 | {% endblock %}
--------------------------------------------------------------------------------
/shop/templates/base.html:
--------------------------------------------------------------------------------
1 | {% load static %}
2 |
3 |
4 |
5 |
6 |
7 | {% block meta %}
8 | {% endblock %}
9 |
10 | Amazon.com:{% block title %} {% endblock %}
11 |
12 |
13 |
14 |
15 |
16 |
56 |
66 |
67 |
68 |
86 |
87 | {% if messages %}
88 |
89 | {% for message in messages %}
90 | -
91 | {{ message }}
92 |
93 | {% endfor %}
94 |
95 | {% endif %}
96 |
97 | {% block content %}
98 | {% endblock %}
99 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
--------------------------------------------------------------------------------
/cart/views.py:
--------------------------------------------------------------------------------
1 | from django.conf import settings
2 | from django.http import JsonResponse
3 | from django.views.decorators.http import require_http_methods
4 | from shop.models import Product
5 | from .models import CartItem, Cart
6 |
7 | # Create your views here.
8 | def get_cart(request):
9 | # create a session with cart
10 | item_count = 0
11 | total = 0
12 | if request.user.is_authenticated:
13 | if Cart.objects.filter(user=request.user):
14 | cart = Cart.objects.filter(user=request.user)[0]
15 | items = CartItem.objects.filter(cart=cart)
16 |
17 | item_count = sum(item.quantity for item in items)
18 | total = sum(item.quantity * item.price for item in items)
19 | else:
20 | cart = request.session.get(settings.CART_SESSION_ID)
21 |
22 | if not cart:
23 | # create a new cart
24 | cart = request.session[settings.CART_SESSION_ID] = {}
25 |
26 | item_count = sum([product['quantity'] for product in cart.values()])
27 | total = sum([product['quantity'] * float(product['price']) for product in cart.values()])
28 |
29 | return JsonResponse({
30 | 'status': 200,
31 | 'item_count': item_count,
32 | 'total': total,
33 | })
34 |
35 | @require_http_methods(['POST'])
36 | def add_to_cart(request):
37 | id = request.POST.get('id')
38 | item = Product.objects.get(id=id)
39 |
40 | if not item:
41 | return JsonResponse({
42 | 'status': 404,
43 | 'msg': 'Oops item not found'
44 | })
45 |
46 | item_id = str(item.id)
47 |
48 | product_quantity = 1
49 | item_stock = item.stock
50 |
51 | if request.user.is_authenticated:
52 | cart = Cart.objects.get(user=request.user)
53 | if cart:
54 | product = CartItem.objects.filter(product=item, cart=cart)
55 | if item.stock >= 1:
56 | if product:
57 | # if product is already present
58 | product = product[0]
59 | product.quantity += 1
60 | product.save()
61 | item.stock -= 1
62 | item.save()
63 |
64 | product_quantity = product.quantity
65 |
66 | else:
67 | # create a new product
68 | new_item = CartItem(product=item, price=float(item.price), quantity=1, cart=cart)
69 | new_item.save()
70 | item.stock -= 1
71 | item.save()
72 | product_quantity = 1
73 | item_stock = item.stock
74 | else:
75 | cart = request.session.get(settings.CART_SESSION_ID)
76 |
77 | if not cart:
78 | # create a new cart
79 | cart = request.session[settings.CART_SESSION_ID] = {}
80 |
81 | # check if item is already in the cart
82 | if item.stock >= 1:
83 | if item_id in cart.keys():
84 | # update the quantity
85 | cart[item_id]['quantity'] += 1
86 | item.stock -= 1
87 | item.save()
88 |
89 | else:
90 | # add as a new item
91 | cart[item_id] = {
92 | "quantity": 1,
93 | "price": str(item.price)
94 | }
95 | item.stock -= 1
96 | item.save()
97 |
98 | product_quantity = cart[item_id]['quantity']
99 | item_stock = item.stock
100 |
101 | # explicitly say session has been modified
102 | request.session.modified = True
103 |
104 | return JsonResponse({
105 | 'status': 200,
106 | 'msg': 'Item Added',
107 | 'product_quantity': product_quantity,
108 | 'item_stock': item_stock
109 | })
110 |
111 | @require_http_methods(['POST'])
112 | def remove_from_cart(request):
113 | id = request.POST.get('id')
114 | item = Product.objects.get(id=id)
115 |
116 | if not item:
117 | return JsonResponse({
118 | 'status': 404,
119 | 'msg': 'Oops item not found'
120 | })
121 |
122 | item_id = str(item.id)
123 |
124 | product_quantity = 1
125 | item_stock = item.stock
126 |
127 | if request.user.is_authenticated:
128 | cart = Cart.objects.get(user=request.user)
129 | if cart:
130 | product = CartItem.objects.filter(product=item, cart=cart)
131 | if product:
132 | # if product is already present
133 | product = product[0]
134 | if not (product.quantity - 1) < 1:
135 | product.quantity -= 1
136 | item.stock += 1
137 | item.save()
138 | product.save()
139 |
140 | product_quantity = product.quantity
141 | item_stock = item.stock
142 |
143 | return JsonResponse({
144 | 'status': 200,
145 | 'msg': 'Item Added',
146 | 'product_quantity': product_quantity,
147 | 'item_stock': item_stock
148 | })
149 |
150 | @require_http_methods(['POST'])
151 | def remove_cart(request):
152 | id = request.POST.get('id')
153 | item = Product.objects.get(id=id)
154 |
155 | if not item:
156 | return JsonResponse({
157 | 'status': 404,
158 | 'msg': 'Oops item not found'
159 | })
160 |
161 | item_id = str(item.id)
162 |
163 | if request.user.is_authenticated:
164 | cart = Cart.objects.get(user=request.user)
165 | if cart:
166 | product = CartItem.objects.filter(product=item, cart=cart)
167 | if product:
168 | product = product[0]
169 | # if product is already present
170 | # first refill the item.stock
171 | item.stock += product.quantity
172 | item.save()
173 | product.delete()
174 | return JsonResponse({
175 | 'status': 200,
176 | 'msg': 'Item Added'
177 | })
--------------------------------------------------------------------------------
/shop/views.py:
--------------------------------------------------------------------------------
1 | from django.shortcuts import render, get_object_or_404, redirect
2 | from django.http import HttpResponse
3 | from .models import Product, Category
4 | from django.views.generic import ListView
5 | from django.urls import reverse
6 | from .forms import SignupForm, SignInForm
7 | from django.contrib import messages
8 | from django.contrib.auth.models import User
9 | from django.contrib.auth.hashers import check_password
10 | from django.contrib.auth import authenticate, login, logout as logout_user
11 | from django.conf import settings
12 | from cart.models import Cart, CartItem
13 | from django.contrib.auth.decorators import login_required
14 |
15 | def home(request):
16 | products = Product.objects.all()
17 | categories = Category.objects.all()
18 | return render(request, 'shop/index.html', {
19 | 'products': products,
20 | 'categories': categories
21 | })
22 |
23 | def product_detail(request, slug):
24 | product = get_object_or_404(Product, slug=slug)
25 | categories = Category.objects.all()
26 | similar_items = Product.objects.filter(category=product.category).exclude(slug=slug)
27 | return render(request, 'shop/detail.html', {
28 | 'product': product,
29 | 'similar_items': similar_items,
30 | 'categories': categories
31 | })
32 |
33 | def category(request, slug):
34 | products = Product.objects.filter(category__slug=slug)
35 | categories = Category.objects.all()
36 | return render(request, 'shop/index.html', {
37 | 'products': products,
38 | 'name': slug,
39 | 'categories': categories
40 | })
41 |
42 | def search(request):
43 | query = request.GET.get('q')
44 | categories = Category.objects.all()
45 | if not query:
46 | return redirect('shop:home')
47 |
48 | # search in title, description, or features
49 | products = Product.objects.filter(category__name__icontains=query) | Product.objects.filter(name__icontains=query) | Product.objects.filter(description__icontains=query) | Product.objects.filter(features__icontains=query)
50 |
51 | return render(request, 'shop/index.html', {
52 | 'products': products,
53 | 'name': 'Search Results',
54 | 'categories': categories
55 | })
56 |
57 | def signup(request):
58 | if request.user.is_authenticated:
59 | return redirect("shop:home")
60 | if request.method == 'POST':
61 | form = SignupForm(request.POST)
62 |
63 | if form.is_valid():
64 | # create a new user
65 | form.save()
66 |
67 | messages.add_message(request, messages.SUCCESS, 'Your Account has been created successfully!')
68 | messages.add_message(request, messages.SUCCESS, 'Now Sign-In to continue...')
69 | return redirect("shop:signin")
70 |
71 | else:
72 | form = SignupForm()
73 | return render(request, 'auth/signup.html', {
74 | 'form': form
75 | })
76 |
77 | def signin(request):
78 | next_url = request.GET.get('next')
79 |
80 | if request.user.is_authenticated:
81 | return redirect("shop:home")
82 | if request.method == 'POST':
83 | form = SignInForm(request.POST)
84 | if form.is_valid():
85 | # check authentication
86 | email=form.cleaned_data.get('email')
87 | password=form.cleaned_data.get('password')
88 | if User.objects.filter(email=email):
89 | user = User.objects.filter(email=email)[0]
90 | # check password
91 | matched = check_password(password, user.password)
92 | if matched:
93 | # valid user
94 | # take cart from session and persist # it in db
95 |
96 | if not Cart.objects.filter(user=user):
97 | cart = Cart(user=user)
98 | cart.save()
99 | else:
100 | cart = Cart.objects.get(user=user)
101 |
102 | session_cart = request.session.get(settings.CART_SESSION_ID)
103 |
104 | if session_cart:
105 | for item_id, item in session_cart.items():
106 | product = Product.objects.get(id=int(item_id))
107 |
108 | if CartItem.objects.filter(product=product, cart=cart):
109 | item_in_cart = CartItem.objects.filter(product=product, cart=cart)[0]
110 | # then the product already # exists
111 | # so increase the quantity
112 | item_in_cart.price = float(item['price'])
113 | item_in_cart.quantity += int(item['quantity'])
114 | item_in_cart.save()
115 | else:
116 | # if item is not present in the cart, then add it
117 | new_item = CartItem(product=product, price=float(item['price']), quantity=int(item['quantity']), cart=cart)
118 |
119 | new_item.save()
120 |
121 | # destroy all items from the session
122 | del request.session[settings.CART_SESSION_ID]
123 | # login
124 | login(request, user)
125 | messages.add_message(request, messages.SUCCESS, "Signed In successfully!")
126 | return redirect(next_url) if next_url else redirect("shop:home")
127 | else:
128 | messages.add_message(request, messages.ERROR, "Email or Password doesn't match!")
129 | else:
130 | messages.add_message(request, messages.ERROR, "Email or Password doesn't match!")
131 | else:
132 | form = SignInForm()
133 | return render(request, 'auth/signin.html', {
134 | 'form': form
135 | })
136 |
137 | def logout(request):
138 | logout_user(request)
139 | return redirect("shop:signin")
140 |
141 | @login_required()
142 | def shopping_cart(request):
143 | cart = Cart.objects.get(user=request.user)
144 | categories = Category.objects.all()
145 | products = CartItem.objects.filter(cart=cart)
146 | return render(request, "shop/shopping_cart.html", {
147 | "products": products,
148 | "name": "Shopping Cart",
149 | 'categories':categories
150 | })
151 |
152 | @login_required
153 | def checkout_page(request):
154 | cart = Cart.objects.get(user=request.user)
155 | CartItem.objects.filter(cart=cart).delete()
156 | return render(request, "shop/checkout.html", {
157 | "name": "Checkout Page"
158 | })
--------------------------------------------------------------------------------
/shop/static/shop/cart.js:
--------------------------------------------------------------------------------
1 | document.addEventListener('DOMContentLoaded', () => {
2 | const item_count = document.querySelector('.item_count');
3 | const addToCartBtns = document.querySelectorAll('.add_to_cart_btn');
4 |
5 | const upBtns = document.querySelectorAll('.up_btn');
6 | const downBtns = document.querySelectorAll('.down_btn');
7 | const numbers = document.querySelectorAll('.number');
8 | const deleteBtns = document.querySelectorAll('.delete_btn');
9 |
10 | const total = document.querySelector('.total');
11 | const item_total_count = document.querySelector('.item_total_count');
12 |
13 | const availability = document.querySelectorAll('.cart_page .availability');
14 |
15 | const getCookie = (name) => {
16 | let cookieValue = null;
17 | if (document.cookie && document.cookie !== '') {
18 | const cookies = document.cookie.split(';');
19 | for (let i = 0; i < cookies.length; i++) {
20 | const cookie = cookies[i].trim();
21 | if (cookie.substring(0, name.length + 1) === (name + '=')) {
22 | cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
23 | break;
24 | }
25 | }
26 | }
27 | return cookieValue;
28 | }
29 | const csrftoken = getCookie('csrftoken');
30 |
31 | const getTotal = (isInCart) => {
32 | // fetch a request to /cart
33 | fetch('/cart')
34 | .then(res => res.json())
35 | .then(data => {
36 | if (data.status === 200) {
37 | if (data.item_count > 99) {
38 | data.item_count = "99+"
39 | }
40 | item_count.innerHTML = data.item_count;
41 | if (isInCart) {
42 | total.innerHTML = `$${data.total}`;
43 | if (data.item_count > 1) {
44 | item_total_count.innerHTML = `${data.item_count} items`;
45 | } else {
46 | item_total_count.innerHTML = `${data.item_count} item`;
47 | }
48 | }
49 | }
50 | })
51 | .catch(e => {
52 | console.log(e)
53 | })
54 | }
55 |
56 | const addToCart = (e, isInCart) => {
57 | let id = e.target.dataset.id;
58 |
59 | const formData = new FormData();
60 | formData.append("id", id);
61 |
62 | // fetch POST request to /cart/add_to_cart/
63 | fetch('/cart/add_to_cart/', {
64 | method: 'POST',
65 | body: formData,
66 | headers: {
67 | 'X-CSRFToken': csrftoken
68 | }
69 | })
70 | .then(res => res.json())
71 | .then(data => {
72 | if (data.status === 200) {
73 | getTotal(isInCart);
74 |
75 | if (isInCart) {
76 | numbers.forEach(number => {
77 | if (number.dataset.id === id) {
78 | number.innerHTML = data.product_quantity
79 | }
80 | });
81 |
82 | if (data.product_quantity > 1) {
83 | downBtns.forEach(btn => {
84 | if (btn.dataset.id === e.target.dataset.id) {
85 | btn.classList.remove('disabled');
86 | }
87 | });
88 | }
89 |
90 | if (data.item_stock <= 0) {
91 | availability.forEach(item => {
92 | if (item.dataset.id === e.target.dataset.id) {
93 | item.innerHTML = 'Out Of Stock';
94 | item.classList.remove('available');
95 | item.classList.add('not_available');
96 | }
97 | });
98 | e.target.classList.add('disabled');
99 | }
100 | }
101 |
102 | if (data.item_stock <= 0) {
103 | e.target.disabled = true;
104 | }
105 | }
106 | })
107 | .catch(e => [
108 | console.log(e)
109 | ]);
110 | }
111 |
112 | const removeFromCart = (e) => {
113 | let id = e.target.dataset.id;
114 |
115 | numbers.forEach(number => {
116 | if (number.dataset.id === id) {
117 | if (parseInt(number.innerHTML) > 1) {
118 |
119 | const formData = new FormData();
120 | formData.append("id", id);
121 |
122 | // fetch POST request to /cart/add_to_cart/
123 | fetch('/cart/remove_from_cart/', {
124 | method: 'POST',
125 | body: formData,
126 | headers: {
127 | 'X-CSRFToken': csrftoken
128 | }
129 | })
130 | .then(res => res.json())
131 | .then(data => {
132 | if (data.status === 200) {
133 | getTotal(true);
134 |
135 | numbers.forEach(number => {
136 | if (number.dataset.id === id) {
137 | if (data.product_quantity > 1) {
138 | number.innerHTML = data.product_quantity;
139 | } else if (data.product_quantity === 1) {
140 | number.innerHTML = data.product_quantity;
141 | e.target.classList.add('disabled');
142 | }
143 | }
144 | });
145 |
146 | if (data.item_stock > 0) {
147 | availability.forEach(item => {
148 | if (item.dataset.id === e.target.dataset.id) {
149 | item.innerHTML = 'In Stock';
150 | item.classList.remove('not_available');
151 | item.classList.add('available');
152 | }
153 | })
154 |
155 | upBtns.forEach(btn => {
156 | if (btn.dataset.id === e.target.dataset.id) {
157 | btn.classList.remove('disabled');
158 | }
159 | });
160 | }
161 | }
162 | })
163 | .catch(e => [
164 | console.log(e)
165 | ]);
166 | }
167 | }
168 | });
169 | }
170 |
171 | const removeCart = (e) => {
172 | let id = e.target.dataset.id;
173 |
174 | const formData = new FormData();
175 | formData.append("id", id);
176 |
177 | // fetch POST request to /cart/add_to_cart/
178 | fetch('/cart/remove_cart/', {
179 | method: 'POST',
180 | body: formData,
181 | headers: {
182 | 'X-CSRFToken': csrftoken
183 | }
184 | })
185 | .then(res => res.json())
186 | .then(data => {
187 | if (data.status === 200) {
188 | window.location.reload();
189 | }
190 | })
191 | .catch(e => [
192 | console.log(e)
193 | ]);
194 | }
195 |
196 | addToCartBtns.forEach((btn) => {
197 | btn.addEventListener('click', function (e) {
198 | addToCart(e, false);
199 | })
200 | });
201 |
202 | upBtns.forEach((btn) => {
203 | btn.addEventListener('click', (e) => {
204 | addToCart(e, true);
205 | })
206 | });
207 |
208 | downBtns.forEach((btn) => {
209 | btn.addEventListener('click', (e) => {
210 | removeFromCart(e);
211 | })
212 | });
213 |
214 | deleteBtns.forEach(btn => {
215 | btn.addEventListener('click', (e) => {
216 | removeCart(e);
217 | })
218 | });
219 |
220 | if (window.location.pathname == '/shopping_cart/') {
221 | getTotal(true);
222 | } else {
223 | getTotal();
224 | }
225 | });
226 |
--------------------------------------------------------------------------------
/shop/static/shop/styles.css:
--------------------------------------------------------------------------------
1 | * {
2 | margin: 0;
3 | padding: 0;
4 | font-family: Arial, Helvetica, sans-serif; }
5 |
6 | body {
7 | width: 100vw;
8 | height: 100vh;
9 | background: #fff;
10 | overflow-x: hidden; }
11 |
12 | a {
13 | text-decoration: none; }
14 |
15 | nav {
16 | display: flex;
17 | align-items: center;
18 | background: #131921;
19 | padding: 0.7rem 1rem; }
20 |
21 | .container {
22 | padding: 0.7rem 2.5rem 0.7rem 2rem;
23 | margin: 0 auto;
24 | min-height: 80vh;
25 | box-sizing: border-box; }
26 |
27 | .msg_container {
28 | padding: 0rem 2.5rem 0rem 2rem;
29 | margin: 0 auto;
30 | box-sizing: border-box; }
31 |
32 | button:hover {
33 | cursor: pointer; }
34 |
35 | button:disabled {
36 | cursor: not-allowed; }
37 |
38 | .amazon_logo {
39 | width: 6.8rem;
40 | margin: 0.5rem 0rem 1.5rem 0rem; }
41 |
42 | .btn {
43 | color: #111111;
44 | padding: 0.4rem;
45 | outline: none;
46 | border-radius: 3px;
47 | font-size: 0.9rem;
48 | background: #F0BF3C;
49 | border: 1px solid #9b7714;
50 | width: 100%; }
51 |
52 | .messages {
53 | display: flex;
54 | flex-direction: column;
55 | list-style-type: none; }
56 | .messages li {
57 | padding: 0.6rem 0.8rem;
58 | background: #ddd;
59 | border-radius: 3px;
60 | border: 1px solid #cec8c8;
61 | margin-bottom: 0.6rem;
62 | margin-top: 0.6rem; }
63 | .messages .error {
64 | background: rgba(255, 0, 0, 0.5);
65 | border: 1px solid #e72020; }
66 | .messages .success {
67 | background: rgba(0, 255, 0, 0.5);
68 | border: 1px solid #0de40d; }
69 |
70 | .navbar__left {
71 | display: flex;
72 | flex: 2 2;
73 | align-items: center; }
74 |
75 | .navbar__center {
76 | display: flex;
77 | flex: 5 5;
78 | align-items: center; }
79 |
80 | .navbar__right {
81 | display: flex;
82 | flex: 3 3;
83 | align-items: center; }
84 | .navbar__right * {
85 | flex: 1 1; }
86 |
87 | .navbar__icon {
88 | color: #fff;
89 | text-align: center;
90 | cursor: pointer;
91 | margin: 0rem 0.5rem 0rem 0rem; }
92 | .navbar__icon .hamburger_btn {
93 | width: 2rem;
94 | border: 0.06rem solid #eee;
95 | border-radius: 0.1rem;
96 | padding: 0.2rem; }
97 |
98 | .navbar__logo {
99 | flex: 1 1; }
100 | .navbar__logo img {
101 | width: 6rem;
102 | height: 1.8rem; }
103 |
104 | .navbar__search {
105 | width: 100%;
106 | margin-right: 1.2rem; }
107 | .navbar__search form {
108 | display: flex;
109 | background: #fff;
110 | border-top-left-radius: 0.25rem;
111 | border-bottom-left-radius: 0.25rem;
112 | border-top-right-radius: 0.25rem;
113 | border-bottom-right-radius: 0.25rem; }
114 | .navbar__search form input {
115 | flex: 9 9;
116 | border: none;
117 | outline: none;
118 | padding: 0.5rem;
119 | font-size: 1rem;
120 | border-top-left-radius: 0.25rem;
121 | border-bottom-left-radius: 0.25rem; }
122 | .navbar__search form button {
123 | border: none;
124 | outline: none;
125 | cursor: pointer;
126 | border-top-right-radius: 0.25rem;
127 | border-bottom-right-radius: 0.25rem;
128 | padding: 0.2rem;
129 | background: #F3A847; }
130 | .navbar__search form button svg {
131 | flex: 1 1;
132 | width: 1.5rem;
133 | padding: 0.1rem 0.2rem; }
134 |
135 | .navbar__link {
136 | color: #fff;
137 | font-size: 0.9rem;
138 | font-weight: 600;
139 | text-align: right; }
140 |
141 | .navbar__cart {
142 | text-align: center; }
143 | .navbar__cart a .item_count {
144 | font-weight: 600;
145 | color: #F3A847;
146 | font-size: 1.1rem;
147 | left: 1.8rem;
148 | bottom: 1.5rem;
149 | position: relative;
150 | z-index: 99; }
151 | .navbar__cart a svg {
152 | color: #fff;
153 | width: 2.3rem;
154 | z-index: 1100; }
155 |
156 | .navbar__search__mobile {
157 | background: #131921;
158 | padding: 0rem 1rem 0.7rem 1rem;
159 | display: none; }
160 |
161 | /* side-box */
162 | .hidden {
163 | display: none; }
164 |
165 | .side_box {
166 | width: 100vw;
167 | height: 100vh;
168 | box-sizing: border-box;
169 | z-index: 999999; }
170 |
171 | .side_box__content {
172 | width: 22rem;
173 | height: 100%;
174 | position: fixed;
175 | top: 0;
176 | left: 0;
177 | overflow-x: hidden;
178 | background: #fff;
179 | z-index: 999999;
180 | display: flex;
181 | flex-direction: column; }
182 | .side_box__content * {
183 | padding: 1rem 1.3rem; }
184 | .side_box__content .container {
185 | width: 90%;
186 | margin: 0 auto; }
187 | .side_box__content .top {
188 | background-color: #232F3E;
189 | font-size: 1.2rem;
190 | color: #fff;
191 | font-weight: 600; }
192 | .side_box__content .shop_by_category {
193 | text-transform: uppercase;
194 | font-size: 0.9rem;
195 | font-weight: 500;
196 | color: #999; }
197 | .side_box__content .category {
198 | color: #111; }
199 | .side_box__content .category:hover {
200 | background-color: #eee; }
201 |
202 | .side_box__background {
203 | background: #111;
204 | width: 100%;
205 | height: 100%;
206 | z-index: 999;
207 | position: fixed;
208 | top: 0;
209 | left: 0;
210 | opacity: 0.5; }
211 |
212 | .results {
213 | margin-bottom: 0.25rem;
214 | padding: 0.5rem 0px;
215 | border-bottom: 2px solid #DDDDDD; }
216 |
217 | .products {
218 | display: flex;
219 | flex-direction: column;
220 | margin-bottom: 1rem; }
221 |
222 | .product {
223 | display: flex;
224 | padding: 1.8rem 1rem;
225 | box-sizing: border-box;
226 | border-bottom: 2px solid #DDDDDD; }
227 | .product .product__left {
228 | width: 180px;
229 | margin-right: 1.8rem; }
230 | .product .product__left img {
231 | width: 100%;
232 | height: 150px;
233 | object-fit: contain;
234 | object-position: center; }
235 | .product .product__right {
236 | flex: 4 4;
237 | display: flex;
238 | flex-direction: column; }
239 | .product .product__right .category {
240 | font-size: 0.9rem;
241 | margin-bottom: 0.3rem;
242 | color: #0066CC;
243 | font-weight: 600; }
244 | .product .product__right .name {
245 | font-size: 1.3rem;
246 | font-weight: normal;
247 | color: #111;
248 | margin-bottom: 0.6rem; }
249 | .product .product__right .name:hover {
250 | color: #F3A847; }
251 | .product .product__right .price {
252 | font-size: 1.5rem;
253 | margin-bottom: 0.6rem; }
254 | .product .product__right .price::before {
255 | content: "$";
256 | font-size: 0.8rem;
257 | position: relative;
258 | bottom: 0.4rem;
259 | left: 0.3rem; }
260 | .product .product__right .price::after {
261 | content: "00";
262 | font-size: 0.8rem;
263 | position: relative;
264 | bottom: 0.4rem;
265 | right: 0.3rem; }
266 | .product .product__right .availability {
267 | font-size: 0.9rem; }
268 |
269 | .sub_total {
270 | display: flex;
271 | justify-content: flex-end;
272 | font-size: 1.3rem;
273 | margin: 1rem 8% 1rem 1rem; }
274 | .sub_total .total {
275 | margin-left: 0.3rem; }
276 |
277 | .cart_page .product__left {
278 | flex: 1 1; }
279 | .cart_page .product__middle {
280 | flex: 3 3;
281 | display: flex;
282 | flex-direction: column; }
283 | .cart_page .product__middle .name {
284 | font-size: 1.3rem;
285 | font-weight: normal;
286 | color: #0066c0;
287 | margin-bottom: 0.6rem; }
288 | .cart_page .product__middle .name:hover {
289 | color: #F3A847; }
290 | .cart_page .product__middle .availability {
291 | font-size: 0.9rem;
292 | margin-bottom: 0.8rem; }
293 | .cart_page .product__middle svg {
294 | width: 1.4rem;
295 | cursor: pointer; }
296 | .cart_page .product__middle svg:hover {
297 | color: #F0BF3C; }
298 | .cart_page .product__middle svg.disabled {
299 | color: #999;
300 | cursor: not-allowed !important;
301 | pointer-events: none; }
302 | .cart_page .product__middle .bottom {
303 | display: flex;
304 | align-items: center; }
305 | .cart_page .product__middle .bottom .quantity {
306 | display: flex;
307 | align-items: center; }
308 | .cart_page .product__middle .bottom .quantity span {
309 | margin-right: 0.4rem; }
310 | .cart_page .product__middle .bottom .quantity .count {
311 | font-size: 0.9rem;
312 | margin-right: 1rem;
313 | display: flex;
314 | flex-direction: column;
315 | align-items: center; }
316 | .cart_page .product__middle .bottom .line {
317 | color: #DDDDDD; }
318 | .cart_page .product__middle .bottom .delete {
319 | margin-left: 1rem;
320 | font-size: 0.9rem; }
321 | .cart_page .product__middle .bottom .delete .delete_btn {
322 | color: #0066CC;
323 | cursor: pointer; }
324 | .cart_page .product__right {
325 | flex: 1 1; }
326 | .cart_page .product__right .price {
327 | font-size: 1.4rem;
328 | color: #C23832; }
329 |
330 | .available {
331 | color: #28b932; }
332 |
333 | .not_available {
334 | color: #ce2727; }
335 |
336 | h4.category {
337 | margin-bottom: 0.25rem;
338 | padding: 0.5rem 0px;
339 | border-bottom: 2px solid #DDDDDD; }
340 | h4.category a {
341 | font-weight: 400;
342 | color: #0066CC; }
343 |
344 | .product_detail {
345 | margin-bottom: 1rem;
346 | border-bottom: 2px solid #DDDDDD;
347 | padding-bottom: 1.5rem;
348 | display: flex;
349 | margin-top: 1.5rem;
350 | padding-bottom: 3rem; }
351 | .product_detail .product_detail__left {
352 | align-self: center; }
353 | .product_detail .product_detail__left .image {
354 | width: 443px;
355 | max-height: 575px;
356 | object-fit: contain;
357 | object-position: center;
358 | margin-right: 2rem; }
359 | .product_detail .product_detail__right {
360 | flex: 5 5; }
361 | .product_detail .product_detail__right .category {
362 | margin-bottom: 0.6rem; }
363 | .product_detail .product_detail__right .name {
364 | margin-bottom: 0.6rem; }
365 | .product_detail .product_detail__right .price {
366 | color: #777;
367 | margin-bottom: 1.5rem; }
368 | .product_detail .product_detail__right .price span {
369 | font-size: 1.5rem;
370 | color: #C23832; }
371 | .product_detail .product_detail__right .price span::before {
372 | content: "$";
373 | font-size: 0.8rem;
374 | position: relative;
375 | bottom: 0.4rem;
376 | left: 0.02rem; }
377 | .product_detail .product_detail__right .price span::after {
378 | content: "00";
379 | font-size: 0.8rem;
380 | position: relative;
381 | bottom: 0.4rem;
382 | right: 0.02rem; }
383 | .product_detail .buttons {
384 | display: flex;
385 | margin-bottom: 1.5rem; }
386 |
387 | .details {
388 | margin-bottom: 1rem; }
389 | .details h4 {
390 | margin-bottom: 0.25rem;
391 | padding: 0.5rem 0px; }
392 | .details li span {
393 | font-size: 1rem;
394 | font-weight: 600; }
395 |
396 | .about h4 {
397 | margin-bottom: 0.25rem;
398 | padding: 0.5rem 0px; }
399 | .about li {
400 | margin-left: 1rem; }
401 |
402 | .add_to_cart_btn, a.go_to_cart_btn {
403 | padding: 0.4rem;
404 | outline: none;
405 | border-radius: 3px;
406 | font-size: 0.9rem; }
407 |
408 | .add_to_cart_btn {
409 | background: #F0BF3C;
410 | border: 1px solid #9b7714; }
411 |
412 | a.go_to_cart_btn {
413 | background: #E8962E;
414 | border: 1px solid #a8650d;
415 | margin-left: 1.2rem;
416 | color: #111; }
417 |
418 | .checkout_btn_div {
419 | display: flex;
420 | justify-content: center;
421 | align-items: center;
422 | margin: 2rem 0rem; }
423 |
424 | .checkout_btn {
425 | width: 20rem;
426 | text-align: center; }
427 |
428 | .similar_items {
429 | margin-bottom: 1rem;
430 | border-bottom: 2px solid #DDDDDD;
431 | padding-bottom: 1.5rem; }
432 | .similar_items h4 {
433 | font-size: 1.4rem;
434 | margin-bottom: 1.5rem; }
435 | .similar_items .items {
436 | display: flex;
437 | flex-wrap: wrap;
438 | justify-content: space-between; }
439 | .similar_items .items .item {
440 | width: 200px;
441 | display: flex;
442 | flex-direction: column;
443 | align-items: flex-start;
444 | margin-bottom: 1.5rem; }
445 | .similar_items .items .item a {
446 | color: #0066CC; }
447 | .similar_items .items .item img {
448 | max-width: 12rem;
449 | max-height: 12rem;
450 | margin-bottom: 0.6rem; }
451 | .similar_items .items .item .price {
452 | margin: 0.4rem 0rem;
453 | font-size: 1.4rem;
454 | color: #C23832; }
455 | .similar_items .items .item .price::before {
456 | content: "$";
457 | font-size: 0.8rem;
458 | position: relative;
459 | bottom: 0.4rem;
460 | left: 0.02rem; }
461 | .similar_items .items .item .price::after {
462 | content: "00";
463 | font-size: 0.8rem;
464 | position: relative;
465 | bottom: 0.4rem;
466 | right: 0.02rem; }
467 |
468 | .description {
469 | margin-bottom: 1rem; }
470 | .description h4 {
471 | margin-bottom: 0.25rem;
472 | padding: 0rem 0rem 0.5rem 0rem; }
473 |
474 | footer {
475 | display: flex;
476 | flex-direction: column;
477 | text-align: center; }
478 | footer .top {
479 | background: #485769;
480 | padding: 1rem 0rem;
481 | color: #FFFCFA;
482 | font-size: 0.9rem; }
483 | footer .center {
484 | display: flex;
485 | justify-content: center;
486 | background: #232F3E;
487 | padding: 3rem 0rem; }
488 | footer .center div {
489 | display: flex; }
490 | footer .center a {
491 | color: #FFFCFA;
492 | font-size: 0.9rem;
493 | margin-right: 1rem; }
494 | footer .center a:hover {
495 | text-decoration: underline; }
496 | footer .bottom {
497 | background: #131A22;
498 | font-size: 0.8rem;
499 | color: #8D9191;
500 | padding: 0.5rem; }
501 |
502 | .signup, .signin {
503 | display: flex;
504 | flex-direction: column;
505 | align-items: center; }
506 | .signup form, .signin form {
507 | display: flex;
508 | flex-direction: column;
509 | border: 1px solid #DDDDDD;
510 | border-radius: 3px;
511 | padding: 2rem 1.5rem;
512 | width: 20rem;
513 | box-sizing: border-box; }
514 | .signup form ul.errors, .signin form ul.errors {
515 | display: flex;
516 | flex-direction: column;
517 | list-style-type: none; }
518 | .signup form ul.errors li.error, .signin form ul.errors li.error {
519 | font-size: 0.9rem;
520 | color: #ff0000;
521 | margin: 0.2rem 0rem 0.8rem 0rem; }
522 | .signup form h1, .signin form h1 {
523 | font-size: 2rem;
524 | font-weight: 500;
525 | margin-bottom: 0.9rem; }
526 | .signup form p, .signin form p {
527 | width: 100%; }
528 | .signup form label, .signin form label {
529 | font-size: 0.9rem;
530 | font-weight: 700;
531 | color: #111;
532 | padding: 0rem 0rem 0.1rem 0.1rem;
533 | margin-bottom: 0.1rem; }
534 | .signup form input, .signin form input {
535 | width: 100%;
536 | margin-top: 0.2rem;
537 | margin-bottom: 0.8rem;
538 | border: 1px solid #8D9191;
539 | font-size: 1rem;
540 | padding: 0.3rem 0.4rem;
541 | border-radius: 3px;
542 | box-sizing: border-box; }
543 | .signup form input:focus, .signin form input:focus {
544 | outline-color: #F3A847; }
545 | .signup form p.agreement, .signin form p.agreement {
546 | font-size: 0.8rem;
547 | margin: 1.2rem 0rem;
548 | padding-bottom: 1.2rem;
549 | border-bottom: 1px solid #DDDDDD; }
550 | .signup form p.extras, .signin form p.extras {
551 | font-size: 0.9rem; }
552 |
553 | @media only screen and (max-width: 52rem) {
554 | .navbar__left {
555 | flex: 1 1; }
556 |
557 | .navbar__center {
558 | display: none; }
559 |
560 | .navbar__right {
561 | flex: 1 1; }
562 |
563 | .navbar__search__mobile {
564 | display: block; }
565 |
566 | .navbar__search__mobile * {
567 | display: block; }
568 |
569 | .product {
570 | padding: 1.8rem 0rem; }
571 |
572 | .product_detail {
573 | width: 100%;
574 | display: flex;
575 | flex-direction: column;
576 | align-items: center; }
577 | .product_detail .product_detail__left .image {
578 | max-width: 100%;
579 | max-height: 575px;
580 | margin: 0rem 0rem 0.6rem 0rem; }
581 | .product_detail .product_detail__right {
582 | flex: 1 1; }
583 | .product_detail .product_detail__right .category {
584 | margin-bottom: 0.6rem; }
585 | .product_detail .product_detail__right .name {
586 | margin-bottom: 0.6rem; }
587 |
588 | .buttons {
589 | justify-content: center; } }
590 |
591 | /*# sourceMappingURL=styles.css.map */
592 |
--------------------------------------------------------------------------------