├── .angular-cli.json
├── .browserslistrc
├── .editorconfig
├── .firebaserc
├── .gitignore
├── LICENSE
├── README.md
├── angular.json
├── database.rules.json
├── e2e
├── app.e2e-spec.ts
├── app.po.ts
└── tsconfig.e2e.json
├── firebase.json
├── functions
├── index.js
├── package-lock.json
└── package.json
├── karma.conf.js
├── ngsw-manifest.json
├── package-lock.json
├── package.json
├── protractor.conf.js
├── scripts
├── hashcode.js
└── static-generator.js
├── src
├── app
│ ├── admin
│ │ ├── admin-components
│ │ │ ├── add-admin
│ │ │ │ ├── add-admin.component.html
│ │ │ │ ├── add-admin.component.scss
│ │ │ │ ├── add-admin.component.spec.ts
│ │ │ │ └── add-admin.component.ts
│ │ │ ├── add-customer
│ │ │ │ ├── add-customer.component.html
│ │ │ │ ├── add-customer.component.scss
│ │ │ │ ├── add-customer.component.spec.ts
│ │ │ │ └── add-customer.component.ts
│ │ │ ├── add-order
│ │ │ │ ├── add-order.component.html
│ │ │ │ ├── add-order.component.scss
│ │ │ │ ├── add-order.component.spec.ts
│ │ │ │ └── add-order.component.ts
│ │ │ ├── add-page
│ │ │ │ ├── add-page.component.html
│ │ │ │ ├── add-page.component.scss
│ │ │ │ ├── add-page.component.spec.ts
│ │ │ │ └── add-page.component.ts
│ │ │ ├── add-post
│ │ │ │ ├── add-post.component.html
│ │ │ │ ├── add-post.component.scss
│ │ │ │ ├── add-post.component.spec.ts
│ │ │ │ └── add-post.component.ts
│ │ │ ├── add-product-category
│ │ │ │ ├── add-product-category.component.html
│ │ │ │ ├── add-product-category.component.scss
│ │ │ │ ├── add-product-category.component.spec.ts
│ │ │ │ └── add-product-category.component.ts
│ │ │ ├── add-product
│ │ │ │ ├── add-product.component.html
│ │ │ │ ├── add-product.component.scss
│ │ │ │ ├── add-product.component.spec.ts
│ │ │ │ └── add-product.component.ts
│ │ │ ├── admin-admins
│ │ │ │ ├── admin-admins.component.html
│ │ │ │ ├── admin-admins.component.scss
│ │ │ │ ├── admin-admins.component.spec.ts
│ │ │ │ └── admin-admins.component.ts
│ │ │ ├── admin-approvals
│ │ │ │ ├── admin-approvals.component.html
│ │ │ │ ├── admin-approvals.component.scss
│ │ │ │ ├── admin-approvals.component.spec.ts
│ │ │ │ └── admin-approvals.component.ts
│ │ │ ├── admin-customers
│ │ │ │ ├── admin-customers.component.html
│ │ │ │ ├── admin-customers.component.scss
│ │ │ │ ├── admin-customers.component.spec.ts
│ │ │ │ └── admin-customers.component.ts
│ │ │ ├── admin-dashboard
│ │ │ │ ├── admin-dashboard.component.html
│ │ │ │ ├── admin-dashboard.component.scss
│ │ │ │ ├── admin-dashboard.component.spec.ts
│ │ │ │ └── admin-dashboard.component.ts
│ │ │ ├── admin-menus
│ │ │ │ ├── admin-menus.component.html
│ │ │ │ ├── admin-menus.component.scss
│ │ │ │ ├── admin-menus.component.spec.ts
│ │ │ │ └── admin-menus.component.ts
│ │ │ ├── admin-orders
│ │ │ │ ├── admin-orders.component.html
│ │ │ │ ├── admin-orders.component.scss
│ │ │ │ ├── admin-orders.component.spec.ts
│ │ │ │ └── admin-orders.component.ts
│ │ │ ├── admin-pages
│ │ │ │ ├── admin-pages.component.html
│ │ │ │ ├── admin-pages.component.scss
│ │ │ │ ├── admin-pages.component.spec.ts
│ │ │ │ └── admin-pages.component.ts
│ │ │ ├── admin-posts
│ │ │ │ ├── admin-posts.component.html
│ │ │ │ ├── admin-posts.component.scss
│ │ │ │ ├── admin-posts.component.spec.ts
│ │ │ │ └── admin-posts.component.ts
│ │ │ ├── admin-product-categories
│ │ │ │ ├── admin-product-categories.component.html
│ │ │ │ ├── admin-product-categories.component.scss
│ │ │ │ ├── admin-product-categories.component.spec.ts
│ │ │ │ └── admin-product-categories.component.ts
│ │ │ ├── admin-products
│ │ │ │ ├── admin-products.component.html
│ │ │ │ ├── admin-products.component.scss
│ │ │ │ ├── admin-products.component.spec.ts
│ │ │ │ └── admin-products.component.ts
│ │ │ ├── admin-theme
│ │ │ │ ├── admin-theme.component.html
│ │ │ │ ├── admin-theme.component.scss
│ │ │ │ ├── admin-theme.component.spec.ts
│ │ │ │ └── admin-theme.component.ts
│ │ │ ├── admin
│ │ │ │ ├── admin.component.html
│ │ │ │ ├── admin.component.scss
│ │ │ │ ├── admin.component.spec.ts
│ │ │ │ └── admin.component.ts
│ │ │ ├── approve-dialog
│ │ │ │ ├── approve-dialog.component.html
│ │ │ │ ├── approve-dialog.component.scss
│ │ │ │ ├── approve-dialog.component.spec.ts
│ │ │ │ └── approve-dialog.component.ts
│ │ │ └── delete-dialog
│ │ │ │ ├── delete-dialog.component.html
│ │ │ │ ├── delete-dialog.component.scss
│ │ │ │ ├── delete-dialog.component.spec.ts
│ │ │ │ └── delete-dialog.component.ts
│ │ ├── admin-routing.module.ts
│ │ └── admin.module.ts
│ ├── app-routing.module.ts
│ ├── app.component.html
│ ├── app.component.scss
│ ├── app.component.spec.ts
│ ├── app.component.ts
│ ├── app.module.ts
│ ├── directives
│ │ ├── stop-propagation.directive.spec.ts
│ │ └── stop-propagation.directive.ts
│ ├── firebase.module.ts
│ ├── materialcomponents.module.ts
│ ├── pipes
│ │ ├── get-key.pipe.spec.ts
│ │ ├── get-key.pipe.ts
│ │ ├── getUser.pipe.spec.ts
│ │ ├── getUser.pipe.ts
│ │ ├── object-count.pipe.spec.ts
│ │ ├── object-count.pipe.ts
│ │ ├── safe-html.pipe.spec.ts
│ │ ├── safe-html.pipe.ts
│ │ ├── search.pipe.spec.ts
│ │ ├── search.pipe.ts
│ │ ├── sort.pipe.spec.ts
│ │ ├── sort.pipe.ts
│ │ ├── truncate.pipe.spec.ts
│ │ └── truncate.pipe.ts
│ ├── services
│ │ ├── admin.guard.spec.ts
│ │ ├── admin.guard.ts
│ │ ├── auth.guard.spec.ts
│ │ ├── auth.guard.ts
│ │ ├── global.service.spec.ts
│ │ ├── global.service.ts
│ │ ├── localcart.service.spec.ts
│ │ ├── localcart.service.ts
│ │ ├── super-admin.guard.spec.ts
│ │ ├── super-admin.guard.ts
│ │ ├── window-ref.service.spec.ts
│ │ └── window-ref.service.ts
│ ├── shared.module.ts
│ └── storefront-components
│ │ ├── cart-icon
│ │ ├── cart-icon.component.html
│ │ ├── cart-icon.component.scss
│ │ ├── cart-icon.component.spec.ts
│ │ └── cart-icon.component.ts
│ │ ├── cart
│ │ ├── cart.component.html
│ │ ├── cart.component.scss
│ │ ├── cart.component.spec.ts
│ │ └── cart.component.ts
│ │ ├── checkout-billing
│ │ ├── checkout-billing.component.html
│ │ ├── checkout-billing.component.scss
│ │ ├── checkout-billing.component.spec.ts
│ │ └── checkout-billing.component.ts
│ │ ├── checkout-confirmation
│ │ ├── checkout-confirmation.component.html
│ │ ├── checkout-confirmation.component.scss
│ │ ├── checkout-confirmation.component.spec.ts
│ │ └── checkout-confirmation.component.ts
│ │ ├── checkout-payment
│ │ ├── checkout-payment.component.html
│ │ ├── checkout-payment.component.scss
│ │ ├── checkout-payment.component.spec.ts
│ │ └── checkout-payment.component.ts
│ │ ├── checkout-review
│ │ ├── checkout-review.component.html
│ │ ├── checkout-review.component.scss
│ │ ├── checkout-review.component.spec.ts
│ │ └── checkout-review.component.ts
│ │ ├── checkout-shipping
│ │ ├── checkout-shipping.component.html
│ │ ├── checkout-shipping.component.scss
│ │ ├── checkout-shipping.component.spec.ts
│ │ └── checkout-shipping.component.ts
│ │ ├── login
│ │ ├── login.component.html
│ │ ├── login.component.scss
│ │ ├── login.component.spec.ts
│ │ └── login.component.ts
│ │ ├── order
│ │ ├── order.component.html
│ │ ├── order.component.scss
│ │ ├── order.component.spec.ts
│ │ └── order.component.ts
│ │ ├── orders
│ │ ├── orders.component.html
│ │ ├── orders.component.scss
│ │ ├── orders.component.spec.ts
│ │ └── orders.component.ts
│ │ ├── page
│ │ ├── page.component.html
│ │ ├── page.component.scss
│ │ ├── page.component.spec.ts
│ │ └── page.component.ts
│ │ ├── pages
│ │ ├── pages.component.html
│ │ ├── pages.component.scss
│ │ ├── pages.component.spec.ts
│ │ └── pages.component.ts
│ │ ├── post
│ │ ├── post.component.html
│ │ ├── post.component.scss
│ │ ├── post.component.spec.ts
│ │ └── post.component.ts
│ │ ├── posts
│ │ ├── posts.component.html
│ │ ├── posts.component.scss
│ │ ├── posts.component.spec.ts
│ │ └── posts.component.ts
│ │ ├── product-categories
│ │ ├── product-categories.component.html
│ │ ├── product-categories.component.scss
│ │ ├── product-categories.component.spec.ts
│ │ └── product-categories.component.ts
│ │ ├── product-category
│ │ ├── product-category.component.html
│ │ ├── product-category.component.scss
│ │ ├── product-category.component.spec.ts
│ │ └── product-category.component.ts
│ │ ├── product
│ │ ├── product.component.html
│ │ ├── product.component.scss
│ │ ├── product.component.spec.ts
│ │ └── product.component.ts
│ │ ├── products
│ │ ├── products.component.html
│ │ ├── products.component.scss
│ │ ├── products.component.spec.ts
│ │ └── products.component.ts
│ │ └── search-results
│ │ ├── search-results.component.html
│ │ ├── search-results.component.scss
│ │ ├── search-results.component.spec.ts
│ │ └── search-results.component.ts
├── assets
│ ├── .gitkeep
│ ├── google-icon.png
│ ├── icons
│ │ ├── icon-144x144.png
│ │ ├── icon-16x16.png
│ │ ├── icon-192x192.png
│ │ ├── icon-32x32.png
│ │ └── icon-512x512.png
│ └── placeholder.jpg
├── favicon.ico
├── index.html
├── main.ts
├── manifest.json
├── polyfills.ts
├── styles.scss
├── test.ts
├── tsconfig.app.json
├── tsconfig.spec.json
├── typings.d.ts
└── variables.scss
├── tsconfig.app.json
├── tsconfig.json
├── tsconfig.spec.json
└── tslint.json
/.angular-cli.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
3 | "project": {
4 | "name": "firebase-cms"
5 | },
6 | "apps": [
7 | {
8 | "root": "src",
9 | "outDir": "dist",
10 | "assets": [
11 | "assets",
12 | "favicon.ico",
13 | "manifest.json"
14 | ],
15 | "index": "index.html",
16 | "main": "main.ts",
17 | "polyfills": "polyfills.ts",
18 | "test": "test.ts",
19 | "tsconfig": "tsconfig.app.json",
20 | "testTsconfig": "tsconfig.spec.json",
21 | "prefix": "app",
22 | "serviceWorker": true,
23 | "styles": [
24 | "styles.scss"
25 | ],
26 | "scripts": [],
27 | "environmentSource": "environments/environment.ts",
28 | "environments": {
29 | "dev": "environments/environment.ts",
30 | "prod": "environments/environment.prod.ts"
31 | }
32 | }
33 | ],
34 | "e2e": {
35 | "protractor": {
36 | "config": "./protractor.conf.js"
37 | }
38 | },
39 | "lint": [
40 | {
41 | "project": "src/tsconfig.app.json"
42 | },
43 | {
44 | "project": "src/tsconfig.spec.json"
45 | },
46 | {
47 | "project": "e2e/tsconfig.e2e.json"
48 | }
49 | ],
50 | "test": {
51 | "karma": {
52 | "config": "./karma.conf.js"
53 | }
54 | },
55 | "defaults": {
56 | "styleExt": "scss",
57 | "component": {
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/.browserslistrc:
--------------------------------------------------------------------------------
1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
2 | # For additional information regarding the format and rule options, please see:
3 | # https://github.com/browserslist/browserslist#queries
4 |
5 | # For the full list of supported browsers by the Angular framework, please see:
6 | # https://angular.io/guide/browser-support
7 |
8 | # You can see what browsers were selected by your queries by running:
9 | # npx browserslist
10 |
11 | last 1 Chrome version
12 | last 1 Firefox version
13 | last 2 Edge major versions
14 | last 2 Safari major versions
15 | last 2 iOS major versions
16 | Firefox ESR
17 | not IE 11 # Angular supports IE 11 only as an opt-in. To opt-in, remove the 'not' prefix on this line.
18 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see https://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 2
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.ts]
12 | quote_type = single
13 |
14 | [*.md]
15 | max_line_length = off
16 | trim_trailing_whitespace = false
17 |
--------------------------------------------------------------------------------
/.firebaserc:
--------------------------------------------------------------------------------
1 | {
2 | "projects": {
3 | "default": "fir-cms-76f54",
4 | "cm": "fir-cms-76f54",
5 | "cms": "fir-cms-76f54"
6 | },
7 | "targets": {
8 | "ng6-fire-shop": {
9 | "hosting": {
10 | "firebase-cms": [
11 | "ng6-fire-shop"
12 | ]
13 | }
14 | }
15 | }
16 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # compiled output
4 | /dist
5 | /tmp
6 | /out-tsc
7 | /static
8 |
9 | # dependencies
10 | /node_modules
11 | /functions/node_modules
12 |
13 | #secrets
14 | /functions/config.json
15 | /functions/.runtimeconfig.json
16 |
17 | # IDEs and editors
18 | /.idea
19 | .project
20 | .classpath
21 | .c9/
22 | *.launch
23 | .settings/
24 | *.sublime-workspace
25 |
26 | # IDE - VSCode
27 | .vscode/*
28 | !.vscode/settings.json
29 | !.vscode/tasks.json
30 | !.vscode/launch.json
31 | !.vscode/extensions.json
32 |
33 | # misc
34 | /.sass-cache
35 | /connect.lock
36 | /coverage
37 | /libpeerconnection.log
38 | npm-debug.log
39 | testem.log
40 | /typings
41 |
42 | # e2e
43 | /e2e/*.js
44 | /e2e/*.map
45 |
46 | # src
47 | /src/environments
48 |
49 | # System Files
50 | .DS_Store
51 | Thumbs.db
52 | /.vs
53 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Alex Abbott
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/e2e/app.e2e-spec.ts:
--------------------------------------------------------------------------------
1 | import { FirebaseCmsPage } from './app.po';
2 |
3 | describe('firebase-cms App', () => {
4 | let page: FirebaseCmsPage;
5 |
6 | beforeEach(() => {
7 | page = new FirebaseCmsPage();
8 | });
9 |
10 | it('should display welcome message', () => {
11 | page.navigateTo();
12 | expect(page.getParagraphText()).toEqual('Welcome to app!!');
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/e2e/app.po.ts:
--------------------------------------------------------------------------------
1 | import { browser, by, element } from 'protractor';
2 |
3 | export class FirebaseCmsPage {
4 | navigateTo() {
5 | return browser.get('/');
6 | }
7 |
8 | getParagraphText() {
9 | return element(by.css('app-root h1')).getText();
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/e2e/tsconfig.e2e.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../out-tsc/e2e",
5 | "module": "commonjs",
6 | "target": "es5",
7 | "types": [
8 | "jasmine",
9 | "jasminewd2",
10 | "node"
11 | ]
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/firebase.json:
--------------------------------------------------------------------------------
1 | {
2 | "database": {
3 | "rules": "database.rules.json"
4 | },
5 | "hosting": {
6 | "public": "dist/firebase-cms",
7 | "cleanUrls": true,
8 | "rewrites": [
9 | {
10 | "source": "**",
11 | "destination": "/index.html"
12 | }
13 | ],
14 | "headers": [
15 | {
16 | "source": "**/*.@(jpg|jpeg|gif|png)",
17 | "headers": [
18 | {
19 | "key": "Cache-Control",
20 | "value": "max-age=7200"
21 | }
22 | ]
23 | },
24 | {
25 | "source": "**",
26 | "headers": [
27 | {
28 | "key": "Content-Security-Policy",
29 | "value": "default-src 'self'; connect-src 'self' ws: wss: http://localhost:4200 https://*.googleapis.com; script-src 'unsafe-inline' 'unsafe-eval' 'self' https://*.google.com https://*.firebaseio.com https://cdn.ckeditor.com https://js.stripe.com/v2/; img-src 'self' data: https://*.googleusercontent.com https://firebasestorage.googleapis.com https://cdn.ckeditor.com https://*.stripe.com; font-src 'self' https://fonts.gstatic.com; style-src 'unsafe-inline' 'self' https://fonts.googleapis.com https://cdn.ckeditor.com; frame-src 'self' https://js.stripe.com https://*.firebaseio.com https://*.firebaseapp.com;"
30 | }
31 | ]
32 | }
33 | ]
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/functions/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "stripe-functions",
3 | "description": "Stripe Firebase Functions",
4 | "dependencies": {
5 | "@google-cloud/logging": "^0.7.1",
6 | "firebase-admin": "^4.0.5",
7 | "firebase-functions": "^0.5.1",
8 | "stripe": "^4.15.0",
9 | "nodemailer": "^4.0.1"
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration file, see link for more information
2 | // https://karma-runner.github.io/1.0/config/configuration-file.html
3 |
4 | module.exports = function (config) {
5 | config.set({
6 | basePath: '',
7 | frameworks: ['jasmine', '@angular-devkit/build-angular'],
8 | plugins: [
9 | require('karma-jasmine'),
10 | require('karma-chrome-launcher'),
11 | require('karma-jasmine-html-reporter'),
12 | require('karma-coverage'),
13 | require('@angular-devkit/build-angular/plugins/karma')
14 | ],
15 | client: {
16 | jasmine: {
17 | // you can add configuration options for Jasmine here
18 | // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html
19 | // for example, you can disable the random execution with `random: false`
20 | // or set a specific seed with `seed: 4321`
21 | },
22 | clearContext: false // leave Jasmine Spec Runner output visible in browser
23 | },
24 | jasmineHtmlReporter: {
25 | suppressAll: true // removes the duplicated traces
26 | },
27 | coverageReporter: {
28 | dir: require('path').join(__dirname, './coverage/reactive-forms-typed-example'),
29 | subdir: '.',
30 | reporters: [
31 | { type: 'html' },
32 | { type: 'text-summary' }
33 | ]
34 | },
35 | reporters: ['progress', 'kjhtml'],
36 | port: 9876,
37 | colors: true,
38 | logLevel: config.LOG_INFO,
39 | autoWatch: true,
40 | browsers: ['Chrome'],
41 | singleRun: false,
42 | restartOnFileChange: true
43 | });
44 | };
45 |
--------------------------------------------------------------------------------
/ngsw-manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "routing": {
3 | "index": "/index.html",
4 | "routes": {
5 | "/": {
6 | "prefix": false
7 | }
8 | }
9 | }
10 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "firebase-cms",
3 | "version": "0.0.0",
4 | "license": "MIT",
5 | "scripts": {
6 | "ng": "ng",
7 | "start": "ng serve",
8 | "build": "ng build",
9 | "test": "ng test",
10 | "lint": "ng lint",
11 | "e2e": "ng e2e",
12 | "hashcode": "node scripts/hashcode.js",
13 | "deploy": "ng build --prod; firebase deploy",
14 | "build-static": "node scripts/static-generator.js",
15 | "deploy-static": "ng build --prod; node scripts/static-generator.js; mv static/* dist/; firebase deploy"
16 | },
17 | "private": true,
18 | "dependencies": {
19 | "@angular/animations": "~11.2.3",
20 | "@angular/cdk": "^11.2.7",
21 | "@angular/common": "~11.2.3",
22 | "@angular/compiler": "~11.2.3",
23 | "@angular/core": "~11.2.3",
24 | "@angular/fire": "^6.1.4",
25 | "@angular/forms": "~11.2.3",
26 | "@angular/material": "^11.2.7",
27 | "@angular/platform-browser": "~11.2.3",
28 | "@angular/platform-browser-dynamic": "~11.2.3",
29 | "@angular/router": "~11.2.3",
30 | "@types/ckeditor": "^4.9.10",
31 | "core-js": "^3.11.0",
32 | "firebase": "^7.0 || ^8.0",
33 | "firebase-admin": "^9.6.0",
34 | "firebase-functions": "^3.13.2",
35 | "ng2-ckeditor": "^1.3.3",
36 | "reactive-forms-typed": "^11.0.4",
37 | "rxjs": "~6.6.0",
38 | "tslib": "^2.0.0",
39 | "zone.js": "~0.11.3"
40 | },
41 | "devDependencies": {
42 | "@angular-devkit/architect": ">= 0.900 < 0.1200",
43 | "@angular-devkit/build-angular": "~0.1102.2",
44 | "@angular/cli": "~11.2.2",
45 | "@angular/compiler-cli": "~11.2.3",
46 | "@types/jasmine": "~3.6.0",
47 | "@types/node": "^12.11.1",
48 | "cheerio": "^1.0.0-rc.6",
49 | "codelyzer": "^6.0.0",
50 | "firebase-tools": "^8.0.0",
51 | "fs": "0.0.1-security",
52 | "fuzzy": "^0.1.3",
53 | "inquirer": "^8.0.0",
54 | "inquirer-autocomplete-prompt": "^1.0.1",
55 | "jasmine-core": "~3.6.0",
56 | "jasmine-spec-reporter": "~5.0.0",
57 | "karma": "~6.1.0",
58 | "karma-chrome-launcher": "~3.1.0",
59 | "karma-coverage": "~2.0.3",
60 | "karma-jasmine": "~4.0.0",
61 | "karma-jasmine-html-reporter": "^1.5.0",
62 | "mkdirp": "^1.0.4",
63 | "nightmare": "^3.0.2",
64 | "open": "^7.0.3",
65 | "protractor": "~7.0.0",
66 | "ts-node": "~8.3.0",
67 | "tslint": "~6.1.0",
68 | "typescript": "~4.1.2"
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/protractor.conf.js:
--------------------------------------------------------------------------------
1 | // Protractor configuration file, see link for more information
2 | // https://github.com/angular/protractor/blob/master/lib/config.ts
3 |
4 | const { SpecReporter } = require('jasmine-spec-reporter');
5 |
6 | exports.config = {
7 | allScriptsTimeout: 11000,
8 | specs: [
9 | './e2e/**/*.e2e-spec.ts'
10 | ],
11 | capabilities: {
12 | 'browserName': 'chrome'
13 | },
14 | directConnect: true,
15 | baseUrl: 'http://localhost:4200/',
16 | framework: 'jasmine',
17 | jasmineNodeOpts: {
18 | showColors: true,
19 | defaultTimeoutInterval: 30000,
20 | print: function() {}
21 | },
22 | onPrepare() {
23 | require('ts-node').register({
24 | project: 'e2e/tsconfig.e2e.json'
25 | });
26 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
27 | }
28 | };
29 |
--------------------------------------------------------------------------------
/scripts/hashcode.js:
--------------------------------------------------------------------------------
1 | const inquirer = require("inquirer");
2 |
3 | inquirer
4 | .prompt([{
5 | type: 'input',
6 | name: 'email',
7 | message: 'Email: '
8 | }])
9 | .then(({email}) => {
10 | const hashCode = getHashCode(email);
11 | console.log('Hashcode: ', hashCode);
12 | })
13 |
14 | function getHashCode(input) {
15 | let hash = 0, i, chr;
16 | if (input.length === 0) return hash;
17 | for (i = 0; i < input.length; i++) {
18 | chr = input.charCodeAt(i);
19 | hash = ((hash << 5) - hash) + chr;
20 | hash |= 0; // Convert to 32bit integer
21 | }
22 | return hash;
23 | }
24 |
--------------------------------------------------------------------------------
/src/app/admin/admin-components/add-admin/add-admin.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Add an
4 | Edit
5 | Admin
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | Editor
14 | Admin
15 | Super Admin
16 |
17 |
18 |
23 |
24 |
--------------------------------------------------------------------------------
/src/app/admin/admin-components/add-admin/add-admin.component.scss:
--------------------------------------------------------------------------------
1 | .admin-form {
2 | box-sizing: border-box;
3 | padding: 0 30px 30px 30px;
4 | }
5 |
6 | button {
7 | margin-top: 30px;
8 | }
9 |
10 | .mat-input-container,
11 | .mat-raised-button {
12 | display: block;
13 | }
14 |
15 | .mat-input-container,
16 | .mat-select {
17 | display: inline-block;
18 | width: 48%;
19 | }
--------------------------------------------------------------------------------
/src/app/admin/admin-components/add-admin/add-admin.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { AddAdminComponent } from './add-admin.component';
4 |
5 | describe('AddAdminComponent', () => {
6 | let component: AddAdminComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async(() => {
10 | TestBed.configureTestingModule({
11 | declarations: [ AddAdminComponent ]
12 | })
13 | .compileComponents();
14 | }));
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(AddAdminComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should be created', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/src/app/admin/admin-components/add-admin/add-admin.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { ActivatedRoute, Params, Router } from '@angular/router';
3 | import { MatSnackBar } from '@angular/material/snack-bar';
4 | import { AngularFireDatabase, QueryFn } from '@angular/fire/database';
5 | import { Observable } from 'rxjs';
6 | import { GlobalService } from '../../../services/global.service';
7 |
8 | @Component({
9 | selector: 'add-admin',
10 | templateUrl: './add-admin.component.html',
11 | styleUrls: ['./add-admin.component.scss']
12 | })
13 | export class AddAdminComponent implements OnInit {
14 |
15 | newEmail: string;
16 | newRole: string;
17 | currentAdmin: Observable;
18 | editMode: boolean;
19 | adminKey: string;
20 |
21 | constructor(
22 | public db: AngularFireDatabase,
23 | public snackBar: MatSnackBar,
24 | public router: Router,
25 | public route: ActivatedRoute,
26 | public globalService: GlobalService
27 | ) {
28 | this.editMode = false;
29 | }
30 |
31 | ngOnInit() {
32 | this.route.params.subscribe((params: Params) => {
33 | if (params && params.key) {
34 | this.editMode = true;
35 | this.adminKey = params.key;
36 | this.currentAdmin = this.db.object('/admins/' + params.key).valueChanges();
37 |
38 | this.currentAdmin.subscribe((a:any) => {
39 | this.newEmail = a.email;
40 | this.newRole = a.role;
41 | });
42 | } else {
43 | this.newEmail = null;
44 | this.newRole = 'editor';
45 | }
46 | });
47 | }
48 |
49 | addAdmin(newEmail: string, newRole: string) {
50 | if (newEmail && newRole) {
51 |
52 | this.db.object('/admins/' + this.globalService.hashCode(newEmail)).update({
53 | email: newEmail,
54 | role: newRole
55 | });
56 |
57 | this.newEmail = null;
58 | this.newRole = null;
59 |
60 | let snackBarRef = this.snackBar.open('Admin saved', 'OK!', {
61 | duration: 3000
62 | });
63 |
64 | } else if (!newEmail) {
65 | let snackBarRef = this.snackBar.open('You must add an email for the user', 'OK!', {
66 | duration: 3000
67 | });
68 | } else if (!newRole) {
69 | let snackBarRef = this.snackBar.open('You must add a role for the user', 'OK!', {
70 | duration: 3000
71 | });
72 | }
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/app/admin/admin-components/add-customer/add-customer.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Add a
4 | Edit
5 | Customer
6 |
7 |
8 |
9 |
10 |
11 |
12 |
17 |
--------------------------------------------------------------------------------
/src/app/admin/admin-components/add-customer/add-customer.component.scss:
--------------------------------------------------------------------------------
1 | .customer-form {
2 | box-sizing: border-box;
3 | padding: 0 30px 30px 30px;
4 | }
5 |
6 | button {
7 | margin-top: 30px;
8 | }
9 |
10 | .mat-input-container,
11 | .mat-raised-button {
12 | display: block;
13 | }
--------------------------------------------------------------------------------
/src/app/admin/admin-components/add-customer/add-customer.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { AddCustomerComponent } from './add-customer.component';
4 |
5 | describe('AddCustomerComponent', () => {
6 | let component: AddCustomerComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async(() => {
10 | TestBed.configureTestingModule({
11 | declarations: [ AddCustomerComponent ]
12 | })
13 | .compileComponents();
14 | }));
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(AddCustomerComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should be created', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/src/app/admin/admin-components/add-customer/add-customer.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { Observable } from 'rxjs';
3 | import { AngularFireDatabase, AngularFireList, AngularFireObject } from '@angular/fire/database';
4 | import { MatSnackBar } from '@angular/material/snack-bar';
5 | import { ActivatedRoute, Params, Router } from '@angular/router';
6 |
7 | @Component({
8 | selector: 'add-customer',
9 | templateUrl: './add-customer.component.html',
10 | styleUrls: ['./add-customer.component.scss']
11 | })
12 | export class AddCustomerComponent implements OnInit {
13 |
14 | customers: AngularFireList;
15 | customer: AngularFireObject;
16 | newEmail: string;
17 | editMode: boolean;
18 | customerKey: string;
19 |
20 | constructor(
21 | public db: AngularFireDatabase,
22 | public snackBar: MatSnackBar,
23 | public router: Router,
24 | public route: ActivatedRoute
25 | ) {
26 | this.customers = db.list('/users');
27 | }
28 |
29 | addCustomer(newEmail: string) {
30 | if (newEmail) {
31 | if (this.editMode && this.customerKey) {
32 | this.db.object('/users/' + this.customerKey).update({
33 | email: newEmail
34 | });
35 | } else {
36 | this.customers.push({
37 | email: newEmail,
38 | status: 'inactive'
39 | });
40 | }
41 |
42 | let snackBarRef = this.snackBar.open('Customer saved', 'OK!', {
43 | duration: 3000
44 | });
45 |
46 | } else if (!newEmail) {
47 | let snackBarRef = this.snackBar.open('You must add an email for this customer', 'OK!', {
48 | duration: 3000
49 | });
50 | }
51 | }
52 |
53 | ngOnInit() {
54 | this.route.params.subscribe((params: Params) => {
55 | if (params && params.uid) {
56 | this.editMode = true;
57 | this.customerKey = params.uid;
58 | this.db.object('/users/' + params.uid).valueChanges().subscribe((u: any) => {
59 | this.newEmail = u.email;
60 | });
61 | } else {
62 | this.newEmail = null;
63 | }
64 | });
65 | }
66 | }
67 |
68 |
--------------------------------------------------------------------------------
/src/app/admin/admin-components/add-order/add-order.component.scss:
--------------------------------------------------------------------------------
1 | .order-form {
2 | box-sizing: border-box;
3 | padding: 0 30px 30px 30px;
4 | }
5 |
6 | h2,
7 | .order-user {
8 | display: inline-block;
9 | width: 49%;
10 | }
11 |
12 | .order-user {
13 | text-align: right;
14 | }
15 |
16 | .order-user span {
17 | font-weight: bold;
18 | }
19 |
20 | .mat-card {
21 | display: inline-block;
22 | width: 43%;
23 | }
24 |
25 | .mat-card:last-of-type {
26 | float: right;
27 | }
28 |
29 | .submit {
30 | margin-top: 30px;
31 | }
32 |
33 | .mat-input-container,
34 | .mat-raised-button {
35 | display: block;
36 | }
37 |
38 | .mat-select {
39 | display: block;
40 | margin: 20px auto 21px auto;
41 | }
--------------------------------------------------------------------------------
/src/app/admin/admin-components/add-order/add-order.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { AddOrderComponent } from './add-order.component';
4 |
5 | describe('AddOrderComponent', () => {
6 | let component: AddOrderComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async(() => {
10 | TestBed.configureTestingModule({
11 | declarations: [ AddOrderComponent ]
12 | })
13 | .compileComponents();
14 | }));
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(AddOrderComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should be created', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/src/app/admin/admin-components/add-order/add-order.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { Observable } from 'rxjs';
3 | import { AngularFireDatabase, AngularFireList } from '@angular/fire/database';
4 | import { MatSnackBar } from '@angular/material/snack-bar';
5 | import { ActivatedRoute, Params, Router } from '@angular/router';
6 | import { GlobalService } from '../../../services/global.service';
7 |
8 | @Component({
9 | selector: 'add-order',
10 | templateUrl: './add-order.component.html',
11 | styleUrls: ['./add-order.component.scss']
12 | })
13 | export class AddOrderComponent implements OnInit {
14 |
15 | orders: AngularFireList;
16 | order: any;
17 | editMode: boolean;
18 | orderKey: string;
19 | states: any;
20 | statuses: Array;
21 | users: AngularFireList;
22 |
23 | constructor(
24 | public db: AngularFireDatabase,
25 | public snackBar: MatSnackBar,
26 | public globalService: GlobalService,
27 | public router: Router,
28 | public route: ActivatedRoute
29 | ) {
30 | this.states = globalService.states;
31 | this.statuses = globalService.orderStatuses;
32 | this.orders = db.list('/orders');
33 | this.users = db.list('/users');
34 | this.order = { shipping: {}, billing: {} };
35 | }
36 |
37 | addOrder(newOrder) {
38 | if (newOrder) {
39 | if (this.editMode && this.orderKey) {
40 | this.db.object('/orders/' + this.orderKey).update(newOrder);
41 | } else {
42 | this.orders.push(newOrder);
43 | }
44 |
45 | let snackBarRef = this.snackBar.open('Order saved', 'OK!', {
46 | duration: 3000
47 | });
48 | }
49 | }
50 |
51 | ngOnInit() {
52 | this.route.params.subscribe((params: Params) => {
53 | if (params && params.key) {
54 | this.editMode = true;
55 | this.orderKey = params.key;
56 | this.db.object('/orders/' + params.key).valueChanges().subscribe((o:any) => {
57 | this.order = o;
58 | });
59 | } else {
60 | this.order = { shipping: {}, billing: {} };
61 | }
62 | });
63 | }
64 |
65 | }
66 |
67 |
--------------------------------------------------------------------------------
/src/app/admin/admin-components/add-page/add-page.component.scss:
--------------------------------------------------------------------------------
1 | @import '../../../../variables.scss';
2 |
3 | .page-form {
4 | box-sizing: border-box;
5 | padding: 0 30px 30px 30px;
6 | }
7 |
8 | .highlight.small {
9 | cursor: pointer;
10 | margin-bottom: 30px;
11 | opacity: .7;
12 |
13 | .action {
14 | color: $color-white;
15 | font-weight: 800;
16 | margin-left: auto;
17 | }
18 | }
19 |
20 | .submit {
21 | display: inline-block;
22 | margin: 30px 10px 0 0;
23 | }
24 |
25 | .mat-input-container {
26 | display: block;
27 | }
28 |
29 | .mat-slide-toggle,
30 | ckeditor {
31 | display: block;
32 | margin-top: 30px;
33 | }
--------------------------------------------------------------------------------
/src/app/admin/admin-components/add-page/add-page.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { AddPageComponent } from './add-page.component';
4 |
5 | describe('AddPageComponent', () => {
6 | let component: AddPageComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async(() => {
10 | TestBed.configureTestingModule({
11 | declarations: [ AddPageComponent ]
12 | })
13 | .compileComponents();
14 | }));
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(AddPageComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should be created', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/src/app/admin/admin-components/add-post/add-post.component.scss:
--------------------------------------------------------------------------------
1 | @import '../../../../variables.scss';
2 |
3 | .post-form {
4 | box-sizing: border-box;
5 | padding: 0 30px 30px 30px;
6 | }
7 |
8 | .highlight.small {
9 | cursor: pointer;
10 | margin-bottom: 30px;
11 | opacity: .7;
12 |
13 | .action {
14 | color: $color-white;
15 | font-weight: 800;
16 | margin-left: auto;
17 | }
18 | }
19 |
20 | .submit {
21 | margin-top: 30px;
22 | }
23 |
24 | .mat-input-container {
25 | display: block;
26 | }
27 |
28 | .mat-slide-toggle,
29 | ckeditor {
30 | display: block;
31 | margin-top: 30px;
32 | }
33 |
34 | .thumbnail {
35 | display: block;
36 | margin-top: 10px;
37 | width: 100px;
38 | }
39 |
40 | .mat-upload-button {
41 | margin-top: 10px;
42 | pointer-events: none;
43 | position: relative;
44 | z-index: 1;
45 | }
46 |
47 | .upload-button {
48 | position: relative;
49 | }
50 |
51 | .native-upload {
52 | cursor: pointer;
53 | height: 38px;
54 | left: 0;
55 | outline: 0;
56 | position: absolute;
57 | top: 10px;
58 | width: 208px;
59 | }
60 |
61 | .no-image {
62 | font-size: 0.8em;
63 | margin: 5px 0 0 38px;
64 | }
65 |
66 | .delete-image {
67 | color: #ff0000;
68 | cursor: pointer;
69 | font-size: 0.8em;
70 | margin: 5px 0 0 0;
71 | }
--------------------------------------------------------------------------------
/src/app/admin/admin-components/add-post/add-post.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { AddPostComponent } from './add-post.component';
4 |
5 | describe('AddPostComponent', () => {
6 | let component: AddPostComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async(() => {
10 | TestBed.configureTestingModule({
11 | declarations: [ AddPostComponent ]
12 | })
13 | .compileComponents();
14 | }));
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(AddPostComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should be created', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/src/app/admin/admin-components/add-product-category/add-product-category.component.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/app/admin/admin-components/add-product-category/add-product-category.component.scss:
--------------------------------------------------------------------------------
1 | @import '../../../../variables.scss';
2 |
3 | .category-form {
4 | box-sizing: border-box;
5 | padding: 0 30px 30px 30px;
6 | }
7 |
8 | .highlight.small {
9 | cursor: pointer;
10 | margin-bottom: 30px;
11 | opacity: .7;
12 |
13 | .action {
14 | color: $color-white;
15 | font-weight: 800;
16 | margin-left: auto;
17 | }
18 | }
19 |
20 | button {
21 | margin-top: 30px;
22 | }
23 |
24 | .mat-input-container {
25 | display: block;
26 | }
--------------------------------------------------------------------------------
/src/app/admin/admin-components/add-product-category/add-product-category.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { AddProductCategoryComponent } from './add-product-category.component';
4 |
5 | describe('AddProductCategoryComponent', () => {
6 | let component: AddProductCategoryComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async(() => {
10 | TestBed.configureTestingModule({
11 | declarations: [ AddProductCategoryComponent ]
12 | })
13 | .compileComponents();
14 | }));
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(AddProductCategoryComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should be created', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/src/app/admin/admin-components/add-product/add-product.component.scss:
--------------------------------------------------------------------------------
1 | @import '../../../../variables.scss';
2 |
3 | .product-form {
4 | box-sizing: border-box;
5 | padding: 0 30px 30px 30px;
6 | }
7 |
8 | .highlight.small {
9 | cursor: pointer;
10 | margin-bottom: 30px;
11 | opacity: .7;
12 |
13 | .action {
14 | color: $color-white;
15 | font-weight: 800;
16 | margin-left: auto;
17 | }
18 | }
19 |
20 | .mat-input-container,
21 | .mat-raised-button {
22 | display: block;
23 | }
24 |
25 | .submit {
26 | display: inline-block;
27 | margin: 30px 10px 0 0;
28 | }
29 |
30 | .currency-label,
31 | .price,
32 | .weight {
33 | display: inline-block;
34 | }
35 |
36 | .mat-slide-toggle,
37 | ckeditor {
38 | display: block;
39 | margin-top: 30px;
40 | }
41 |
42 | .thumbnail {
43 | display: block;
44 | margin-top: 10px;
45 | width: 100px;
46 | }
47 |
48 | .mat-upload-button {
49 | margin-top: 10px;
50 | }
51 |
52 | .native-upload {
53 | display: none;
54 | }
55 |
56 | .no-image {
57 | font-size: 0.8em;
58 | margin: 5px 0 0 38px;
59 | }
60 |
61 | .delete-image {
62 | color: $color-red;
63 | cursor: pointer;
64 | font-size: 0.8em;
65 | margin: 5px 0 0 0;
66 | }
--------------------------------------------------------------------------------
/src/app/admin/admin-components/add-product/add-product.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { AddProductComponent } from './add-product.component';
4 |
5 | describe('AddProductComponent', () => {
6 | let component: AddProductComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async(() => {
10 | TestBed.configureTestingModule({
11 | declarations: [ AddProductComponent ]
12 | })
13 | .compileComponents();
14 | }));
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(AddProductComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should be created', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/src/app/admin/admin-components/admin-admins/admin-admins.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Admins
4 |
5 |
6 |
7 |
8 |
9 |
.photoURL }})
10 |

11 | {{admin.payload.val().email}}
12 |
13 |
14 |
15 | {{ admin.payload.val().role }}
16 |
17 | (activeinactive)
18 |
19 |
20 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/src/app/admin/admin-components/admin-admins/admin-admins.component.scss:
--------------------------------------------------------------------------------
1 | @import '../../../../variables.scss';
2 |
3 | .button-container {
4 | h2 {
5 | display: inline-block;
6 | }
7 |
8 | button {
9 | float: right;
10 | margin: 18px 30px 30px 30px;
11 | }
12 | }
13 |
14 | .admin {
15 | .content {
16 | display: inline-block;
17 | margin-top: 13px;
18 | vertical-align: top;
19 | width: calc(100% - 210px);
20 | }
21 |
22 | .active {
23 | display: inline-block;
24 | text-align: center;
25 | width: 200px;
26 |
27 | span {
28 | font-size: .8em;
29 |
30 | &.active-user {
31 | color: $color-green;
32 | }
33 |
34 | &.inactive-user {
35 | color: $color-grey;
36 | }
37 | }
38 |
39 | button {
40 | margin: 10px 0 0 0;
41 | }
42 | }
43 | }
44 |
45 | .admin-photo {
46 | border-radius: 50%;
47 | height: 45px;
48 | margin-right: 15px;
49 | vertical-align: middle;
50 | width: 45px;
51 | }
--------------------------------------------------------------------------------
/src/app/admin/admin-components/admin-admins/admin-admins.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { AdminAdminsComponent } from './admin-admins.component';
4 |
5 | describe('AdminAdminsComponent', () => {
6 | let component: AdminAdminsComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async(() => {
10 | TestBed.configureTestingModule({
11 | declarations: [ AdminAdminsComponent ]
12 | })
13 | .compileComponents();
14 | }));
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(AdminAdminsComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should be created', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/src/app/admin/admin-components/admin-admins/admin-admins.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { Observable } from 'rxjs';
3 | import { AngularFireDatabase } from '@angular/fire/database';
4 | import { MatSnackBar } from '@angular/material/snack-bar';
5 | import { MatDialog, MatDialogRef } from '@angular/material/dialog';
6 | import { DeleteDialogComponent } from '../delete-dialog/delete-dialog.component'
7 |
8 | @Component({
9 | selector: 'admin-admins',
10 | templateUrl: './admin-admins.component.html',
11 | styleUrls: ['./admin-admins.component.scss']
12 | })
13 | export class AdminAdminsComponent {
14 |
15 | admins: Observable;
16 | selectedOption: any;
17 | dialogRef: MatDialogRef;
18 | adminsObject: any;
19 |
20 | constructor(public db: AngularFireDatabase, public dialog: MatDialog, public snackBar: MatSnackBar) {
21 | this.admins = db.list('/admins', ref => ref.orderByChild('email').limitToFirst(9999)).snapshotChanges();
22 |
23 | this.admins.subscribe((adminList:any) => {
24 | this.adminsObject = adminList;
25 | });
26 | }
27 |
28 | countAdmin(email:string) {
29 | let matches = this.adminsObject.filter((item) => {
30 | return item.payload.val().email === email;
31 | });
32 | return matches.length;
33 | }
34 |
35 | deleteAdmin(event, key: string) {
36 | event.stopPropagation();
37 | let dialogRef = this.dialog.open(DeleteDialogComponent);
38 | dialogRef.afterClosed().subscribe(result => {
39 | this.selectedOption = result;
40 | if (this.selectedOption === 'delete') {
41 | this.db.object('/admins/' + key).remove();
42 |
43 | let snackBarRef = this.snackBar.open('Admin deleted', 'OK!', {
44 | duration: 3000
45 | });
46 | }
47 | });
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/app/admin/admin-components/admin-approvals/admin-approvals.component.scss:
--------------------------------------------------------------------------------
1 | @import '../../../../variables.scss';
2 |
3 | .button-container {
4 | h2 {
5 | display: inline-block;
6 | }
7 | }
8 |
9 | .highlight {
10 | div {
11 | &:nth-of-type(1) {
12 | width: 25%;
13 | }
14 |
15 | &:nth-of-type(2) {
16 | width: 22%;
17 | }
18 | }
19 | }
20 |
21 | section {
22 | margin-bottom: 30px;
23 | }
24 |
25 | .approval {
26 | cursor: pointer;
27 | padding: 24px 16px;
28 |
29 | .content {
30 | display: inline-block;
31 | width: 100%;
32 |
33 | a {
34 | float: left;
35 | width: 25%;
36 | }
37 |
38 | .date {
39 | color: $color-grey;
40 | float: left;
41 | font-size: .8em;
42 | width: 22%;
43 | }
44 |
45 | .updated-by {
46 | display: inline-block;;
47 | font-size: .8em;
48 | margin-right: 20px;
49 | width: 25%;
50 | }
51 | }
52 |
53 | .actions {
54 | display: inline-block;
55 | text-align: right;
56 | vertical-align: top;
57 | width: 24%;
58 | }
59 | }
--------------------------------------------------------------------------------
/src/app/admin/admin-components/admin-approvals/admin-approvals.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { AdminApprovalsComponent } from './admin-approvals.component';
4 |
5 | describe('AdminApprovalsComponent', () => {
6 | let component: AdminApprovalsComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async(() => {
10 | TestBed.configureTestingModule({
11 | declarations: [ AdminApprovalsComponent ]
12 | })
13 | .compileComponents();
14 | }));
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(AdminApprovalsComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should be created', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/src/app/admin/admin-components/admin-customers/admin-customers.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Customers
4 |
5 |
6 |
7 | No customers yet
8 |
9 |
10 |
18 |
19 | {{customer.payload.val().orders | objectCount}} orders
20 |
23 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/src/app/admin/admin-components/admin-customers/admin-customers.component.scss:
--------------------------------------------------------------------------------
1 | .button-container{
2 | h2 {
3 | display: inline-block;
4 | }
5 |
6 | button {
7 | float: right;
8 | margin: 18px 30px 30px 30px;
9 | }
10 | }
11 |
12 | .customer {
13 | cursor: pointer;
14 |
15 | .content {
16 | display: inline-block;
17 | margin-top: 13px;
18 | vertical-align: top;
19 | width: calc(100% - 220px);
20 | }
21 |
22 | .active {
23 | display: inline-block;
24 | text-align: center;
25 | width: 210px;
26 |
27 | span {
28 | font-size: .9em;
29 | }
30 |
31 | button {
32 | margin-top: 10px;
33 | }
34 | }
35 | }
36 |
37 | .customer-photo {
38 | border-radius: 50%;
39 | height: 45px;
40 | margin-right: 15px;
41 | vertical-align: middle;
42 | width: 45px;
43 | }
--------------------------------------------------------------------------------
/src/app/admin/admin-components/admin-customers/admin-customers.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { AdminCustomersComponent } from './admin-customers.component';
4 |
5 | describe('AdminCustomersComponent', () => {
6 | let component: AdminCustomersComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async(() => {
10 | TestBed.configureTestingModule({
11 | declarations: [ AdminCustomersComponent ]
12 | })
13 | .compileComponents();
14 | }));
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(AdminCustomersComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should be created', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/src/app/admin/admin-components/admin-customers/admin-customers.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { AngularFireDatabase, AngularFireList } from '@angular/fire/database';
3 | import { MatSnackBar } from '@angular/material/snack-bar';
4 | import { MatDialog, MatDialogRef } from '@angular/material/dialog';
5 | import { DeleteDialogComponent } from '../delete-dialog/delete-dialog.component';
6 | import { GlobalService } from '../../../services/global.service';
7 | import { Observable } from 'rxjs';
8 |
9 | @Component({
10 | selector: 'admin-customers',
11 | templateUrl: './admin-customers.component.html',
12 | styleUrls: ['./admin-customers.component.scss']
13 | })
14 | export class AdminCustomersComponent {
15 |
16 | customers: Observable;
17 | selectedOption: any;
18 | dialogRef: MatDialogRef;
19 | currentAdmin: any;
20 |
21 | constructor(
22 | public db: AngularFireDatabase,
23 | public dialog: MatDialog,
24 | public snackBar: MatSnackBar,
25 | public globalService: GlobalService
26 | ) {
27 | this.customers = db.list('/users').snapshotChanges();
28 |
29 | this.globalService.admin.subscribe((a) => {
30 | this.currentAdmin = a;
31 | });
32 | }
33 |
34 | deleteCustomer(event, key: string) {
35 | event.stopPropagation();
36 | let dialogRef = this.dialog.open(DeleteDialogComponent);
37 | dialogRef.afterClosed().subscribe(result => {
38 | this.selectedOption = result;
39 | if (this.selectedOption === 'delete') {
40 | this.db.object('/users/' + key).remove();
41 |
42 | let snackBarRef = this.snackBar.open('Customer deleted', 'OK!', {
43 | duration: 3000
44 | });
45 | }
46 | });
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/app/admin/admin-components/admin-dashboard/admin-dashboard.component.scss:
--------------------------------------------------------------------------------
1 | @import '../../../../variables.scss';
2 |
3 | .mat-card {
4 | cursor: pointer;
5 | height: 70%;
6 | text-align: center;
7 | width: 80%;
8 |
9 | .mat-icon {
10 | color: $color-main;
11 | font-size: 3em;
12 | width: 100%;
13 | }
14 | }
--------------------------------------------------------------------------------
/src/app/admin/admin-components/admin-dashboard/admin-dashboard.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { AdminDashboardComponent } from './admin-dashboard.component';
4 |
5 | describe('AdminDashboardComponent', () => {
6 | let component: AdminDashboardComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async(() => {
10 | TestBed.configureTestingModule({
11 | declarations: [ AdminDashboardComponent ]
12 | })
13 | .compileComponents();
14 | }));
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(AdminDashboardComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should be created', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/src/app/admin/admin-components/admin-dashboard/admin-dashboard.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { AngularFireDatabase } from '@angular/fire/database';
3 | import { GlobalService } from '../../../services/global.service';
4 | import { Observable } from 'rxjs';
5 |
6 | @Component({
7 | selector: 'admin-dashboard',
8 | templateUrl: './admin-dashboard.component.html',
9 | styleUrls: ['./admin-dashboard.component.scss']
10 | })
11 | export class AdminDashboardComponent implements OnInit {
12 |
13 | posts: Observable;
14 | pages: Observable;
15 | admins: Observable;
16 | products: Observable;
17 | customers: Observable;
18 | categories: Observable;
19 | orders: Observable;
20 | approvals: Observable;
21 | approvalsTotal: number;
22 | currentAdmin: any;
23 | columns: number;
24 |
25 | constructor(public db: AngularFireDatabase, public globalService: GlobalService) {
26 | this.posts = db.list('/posts').valueChanges();
27 | this.pages = db.list('/pages').valueChanges();
28 | this.admins = db.list('/admins').valueChanges();
29 | this.customers = db.list('/users').valueChanges();
30 | this.products = db.list('/products').valueChanges();
31 | this.categories = db.list('/categories').valueChanges();
32 | this.orders = db.list('/orders').valueChanges();
33 | this.approvals = db.object('/approvals').valueChanges();
34 |
35 | this.posts.subscribe
36 |
37 | this.columns = 3;
38 | this.approvalsTotal = 0;
39 |
40 | this.globalService.admin.subscribe((a) => {
41 | this.currentAdmin = a;
42 | });
43 | }
44 |
45 | ngOnInit() {
46 | this.approvals.subscribe((a:any) => {
47 | if (a && a.products) {
48 | this.approvalsTotal += Object.keys(a.products).length;
49 | }
50 | if (a && a.pages) {
51 | this.approvalsTotal += Object.keys(a.pages).length;
52 | }
53 | if (a && a.posts) {
54 | this.approvalsTotal += Object.keys(a.posts).length;
55 | }
56 | });
57 | }
58 |
59 | onResize(event) {
60 | const element = event.target.innerWidth;
61 |
62 | if (element < 950) {
63 | this.columns = 2;
64 | }
65 |
66 | if (element > 950) {
67 | this.columns = 3;
68 | }
69 |
70 | if (element < 750) {
71 | this.columns = 1;
72 | }
73 | }
74 |
75 | }
76 |
--------------------------------------------------------------------------------
/src/app/admin/admin-components/admin-menus/admin-menus.component.html:
--------------------------------------------------------------------------------
1 |
2 |
Menus
3 |
4 |
Primary Nav
5 |
6 | No menu items yet
7 |
8 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/src/app/admin/admin-components/admin-menus/admin-menus.component.scss:
--------------------------------------------------------------------------------
1 | @import '../../../../variables.scss';
2 |
3 | .mat-card {
4 | cursor: move !important;
5 | }
6 |
7 | .mat-icon {
8 | color: $color-grey;
9 | display: block;
10 | margin: 0 auto;
11 | pointer-events: none;
12 | }
13 |
14 | .button-add-menu {
15 | margin-top: 20px;
16 | }
17 |
18 | mat-toolbar {
19 | margin-bottom: 5px;
20 | }
21 |
22 | .actions {
23 | float: right;
24 | }
--------------------------------------------------------------------------------
/src/app/admin/admin-components/admin-menus/admin-menus.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { AdminMenusComponent } from './admin-menus.component';
4 |
5 | describe('AdminMenusComponent', () => {
6 | let component: AdminMenusComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async(() => {
10 | TestBed.configureTestingModule({
11 | declarations: [ AdminMenusComponent ]
12 | })
13 | .compileComponents();
14 | }));
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(AdminMenusComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should be created', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/src/app/admin/admin-components/admin-menus/admin-menus.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { AngularFireDatabase, AngularFireList } from '@angular/fire/database';
3 | import { MatSnackBar } from '@angular/material/snack-bar';
4 | import { MatDialog, MatDialogRef } from '@angular/material/dialog';
5 | import { DeleteDialogComponent } from '../delete-dialog/delete-dialog.component';
6 | import { Observable } from 'rxjs';
7 |
8 | @Component({
9 | selector: 'app-admin-menus',
10 | templateUrl: './admin-menus.component.html',
11 | styleUrls: ['./admin-menus.component.scss']
12 | })
13 | export class AdminMenusComponent implements OnInit {
14 |
15 | nav: Observable;
16 | selectedOption: any;
17 | dialogRef: MatDialogRef;
18 | menuList: any;
19 | menuObject: any;
20 |
21 | constructor(
22 | public db: AngularFireDatabase,
23 | public snackBar: MatSnackBar,
24 | public dialog: MatDialog
25 | ) {
26 | this.menuList = [];
27 | this.menuObject = {};
28 | this.nav = db.list('/menus/nav').valueChanges();
29 |
30 | this.nav.subscribe(items => {
31 | this.menuList = [];
32 | for (let x = 0; x < items.length; x++) {
33 | this.menuList.push(items[x]);
34 | }
35 | });
36 | }
37 |
38 | addMenuItem() {
39 | this.menuList.push({
40 | label: '',
41 | url: ''
42 | });
43 | }
44 |
45 | saveMenu() {
46 | this.menuObject = this.menuList.reduce(function(acc, cur, i) {
47 | acc[i] = cur;
48 | return acc;
49 | }, {});
50 | this.db.object('/menus/nav').set(this.menuObject);
51 | let snackBarRef = this.snackBar.open('Menu saved', 'OK!', {
52 | duration: 3000
53 | });
54 | }
55 |
56 | saveMenuItem(key: string, newLabel: string, newURL: string) {
57 | this.db.object('/menus/nav/' + key).update({
58 | label: newLabel,
59 | url: newURL
60 | });
61 | let snackBarRef = this.snackBar.open('Menu item saved', 'OK!', {
62 | duration: 3000
63 | });
64 | }
65 |
66 | deleteMenuItem(index: Number) {
67 | this.menuList.splice(index, 1);
68 | }
69 |
70 | ngOnInit() {
71 | }
72 |
73 | }
74 |
--------------------------------------------------------------------------------
/src/app/admin/admin-components/admin-orders/admin-orders.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Orders
4 |
5 |
6 |
7 | No orders yet
8 |
9 |
10 |
11 |
12 |
{{order.payload.val().date | date }}
13 |
14 |
15 |
16 |
22 |
25 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/src/app/admin/admin-components/admin-orders/admin-orders.component.scss:
--------------------------------------------------------------------------------
1 | @import '../../../../variables.scss';
2 |
3 | .button-container {
4 | h2 {
5 | display: inline-block;
6 | }
7 |
8 | button {
9 | float: right;
10 | margin: 18px 30px 30px 30px;
11 | }
12 | }
13 |
14 | .order {
15 | cursor: pointer;
16 |
17 | .content {
18 | display: inline-block;
19 | width: calc(100% - 220px);
20 |
21 | .date {
22 | color: $color-grey;
23 | font-size: .8em;
24 | }
25 | }
26 |
27 | .action {
28 | display: inline-block;
29 | margin-top: 15px;
30 | vertical-align: top;
31 | width: 210px;
32 | }
33 | }
34 |
35 | .view {
36 | text-align: center;
37 |
38 | button {
39 | margin-top: 20px;
40 | }
41 | }
--------------------------------------------------------------------------------
/src/app/admin/admin-components/admin-orders/admin-orders.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { AdminOrdersComponent } from './admin-orders.component';
4 |
5 | describe('AdminOrdersComponent', () => {
6 | let component: AdminOrdersComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async(() => {
10 | TestBed.configureTestingModule({
11 | declarations: [ AdminOrdersComponent ]
12 | })
13 | .compileComponents();
14 | }));
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(AdminOrdersComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should be created', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/src/app/admin/admin-components/admin-orders/admin-orders.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { AngularFireDatabase } from '@angular/fire/database';
3 | import { MatSnackBar } from '@angular/material/snack-bar';
4 | import { MatDialog, MatDialogRef } from '@angular/material/dialog';
5 | import { DeleteDialogComponent } from '../delete-dialog/delete-dialog.component'
6 | import { GlobalService } from '../../../services/global.service';
7 | import { ActivatedRoute, Params, Router } from '@angular/router';
8 | import { Observable } from 'rxjs';
9 |
10 | @Component({
11 | selector: 'admin-orders',
12 | templateUrl: './admin-orders.component.html',
13 | styleUrls: ['./admin-orders.component.scss']
14 | })
15 | export class AdminOrdersComponent implements OnInit {
16 |
17 | orders: Observable;
18 | users: Observable;
19 | selectedOption: String;
20 | currentAdmin: any;
21 |
22 | constructor(
23 | public db: AngularFireDatabase,
24 | public dialog: MatDialog,
25 | public snackBar: MatSnackBar,
26 | public globalService: GlobalService,
27 | public router: Router,
28 | public route: ActivatedRoute
29 | ) {
30 |
31 | this.users = db.list('/users').valueChanges();
32 |
33 | if (router.url.includes('customer')) {
34 | this.route.params.subscribe((params: Params) => {
35 | this.orders = db.list('/orders', ref => ref.orderByChild('uid').equalTo(params.key).limitToFirst(99999)).snapshotChanges();
36 | });
37 | } else {
38 | this.orders = db.list('/orders', ref => ref.orderByChild('rdate').limitToFirst(99999)).snapshotChanges();
39 | }
40 |
41 | this.globalService.admin.subscribe((a) => {
42 | this.currentAdmin = a;
43 | });
44 | }
45 |
46 | ngOnInit() {
47 | }
48 |
49 | deleteOrder(event, key: string) {
50 | event.stopPropagation();
51 | let dialogRef = this.dialog.open(DeleteDialogComponent);
52 | dialogRef.afterClosed().subscribe(result => {
53 | this.selectedOption = result;
54 | if (this.selectedOption === 'delete') {
55 | this.db.object('/orders/' + key).remove();
56 | this.db.object('/users/orders/' + key).remove();
57 |
58 | let snackBarRef = this.snackBar.open('Order deleted', 'OK!', {
59 | duration: 3000
60 | });
61 | }
62 | });
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/app/admin/admin-components/admin-pages/admin-pages.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Pages
4 |
5 |
6 |
7 | No pages yet
8 |
9 |
10 |
13 |
14 |
15 | Publish
16 | Un-Publish
17 |
18 |
19 |
22 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/src/app/admin/admin-components/admin-pages/admin-pages.component.scss:
--------------------------------------------------------------------------------
1 | @import '../../../../variables.scss';
2 |
3 | .button-container {
4 | h2 {
5 | display: inline-block;
6 | }
7 |
8 | button {
9 | float: right;
10 | margin: 18px 30px 30px 30px;
11 | }
12 | }
13 |
14 | .page {
15 | cursor: pointer;
16 |
17 | .content {
18 | display: inline-block;
19 | width: calc(100% - 410px);
20 |
21 | h3 {
22 | margin: 0 0 10px 0;
23 | }
24 |
25 | .date {
26 | color: $color-grey;
27 | font-size: .8em;
28 | }
29 | }
30 |
31 | .publish {
32 | float: right;
33 | text-align: right;
34 | vertical-align: top;
35 | width: 400px;
36 | }
37 | }
38 |
39 | .mat-slide-toggle {
40 | margin: 0 20px 0 45px;
41 | width: 130px;
42 | }
43 |
44 | .edit {
45 | display: inline-block;
46 | text-align: center;
47 | }
--------------------------------------------------------------------------------
/src/app/admin/admin-components/admin-pages/admin-pages.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { AdminPagesComponent } from './admin-pages.component';
4 |
5 | describe('AdminPagesComponent', () => {
6 | let component: AdminPagesComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async(() => {
10 | TestBed.configureTestingModule({
11 | declarations: [ AdminPagesComponent ]
12 | })
13 | .compileComponents();
14 | }));
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(AdminPagesComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should be created', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/src/app/admin/admin-components/admin-pages/admin-pages.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { AngularFireDatabase, AngularFireList, AngularFireObject } from '@angular/fire/database';
3 | import { Router } from '@angular/router';
4 | import { GlobalService } from '../../../services/global.service';
5 | import { MatSnackBar } from '@angular/material/snack-bar';
6 | import { MatDialog, MatDialogRef } from '@angular/material/dialog';
7 | import { DeleteDialogComponent } from '../delete-dialog/delete-dialog.component'
8 | import { Observable } from 'rxjs';
9 |
10 | @Component({
11 | selector: 'admin-pages',
12 | templateUrl: './admin-pages.component.html',
13 | styleUrls: ['./admin-pages.component.scss']
14 | })
15 | export class AdminPagesComponent implements OnInit {
16 |
17 | pages: Observable;
18 | page: AngularFireObject;
19 | selectedOption: any;
20 | dialogRef: MatDialogRef;
21 | currentAdmin: any;
22 |
23 | constructor(
24 | public db: AngularFireDatabase,
25 | public router: Router,
26 | public dialog: MatDialog,
27 | public snackBar: MatSnackBar,
28 | public globalService: GlobalService
29 | ) {
30 | this.pages = db.list('/pages', ref => ref.orderByChild('rdateUpdated').limitToFirst(9999)).snapshotChanges();
31 |
32 | this.globalService.admin.subscribe((a) => {
33 | this.currentAdmin = a;
34 | });
35 | }
36 |
37 | onChange(e: any, key: string) {
38 | this.page = this.db.object('/pages/' + key);
39 | if (e.checked) {
40 | this.page.update({published: true});
41 | } else {
42 | this.page.update({published: false});
43 | }
44 | }
45 |
46 | deletePage(key: string) {
47 | let dialogRef = this.dialog.open(DeleteDialogComponent);
48 | dialogRef.afterClosed().subscribe(result => {
49 | this.selectedOption = result;
50 | if (this.selectedOption === 'delete') {
51 | this.db.object('/pages/' + key).remove();
52 |
53 | let snackBarRef = this.snackBar.open('Page deleted', 'OK!', {
54 | duration: 3000
55 | });
56 | }
57 | });
58 | }
59 |
60 | ngOnInit() {
61 | }
62 |
63 | }
64 |
--------------------------------------------------------------------------------
/src/app/admin/admin-components/admin-posts/admin-posts.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Posts
4 |
5 |
6 |
7 | No posts yet
8 |
9 |
10 |
11 |
12 |
{{post.payload.val().date | date }}
13 |
14 |
15 |
16 | Publish
17 | Un-Publish
18 |
19 |
20 |
23 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/src/app/admin/admin-components/admin-posts/admin-posts.component.scss:
--------------------------------------------------------------------------------
1 | @import '../../../../variables.scss';
2 |
3 | .button-container {
4 | h2 {
5 | display: inline-block;
6 | }
7 |
8 | button {
9 | float: right;
10 | margin: 18px 30px 30px 30px;
11 | }
12 | }
13 |
14 | .post {
15 | cursor: pointer;
16 |
17 | .content {
18 | display: inline-block;
19 | width: calc(100% - 410px);
20 |
21 | h3 {
22 | margin: 0 0 10px 0;
23 | }
24 |
25 | .date {
26 | color: $color-grey;
27 | font-size: .8em;
28 | }
29 | }
30 |
31 | .publish {
32 | float: right;
33 | margin-top: 10px;
34 | text-align: right;
35 | vertical-align: top;
36 | width: 400px;
37 | }
38 | }
39 |
40 | .mat-slide-toggle {
41 | margin: 0 20px 0 45px;
42 | width: 130px;
43 | }
44 |
45 | .edit {
46 | display: inline-block;
47 | text-align: center;
48 | }
--------------------------------------------------------------------------------
/src/app/admin/admin-components/admin-posts/admin-posts.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { AdminPostsComponent } from './admin-posts.component';
4 |
5 | describe('AdminPostsComponent', () => {
6 | let component: AdminPostsComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async(() => {
10 | TestBed.configureTestingModule({
11 | declarations: [ AdminPostsComponent ]
12 | })
13 | .compileComponents();
14 | }));
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(AdminPostsComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should be created', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/src/app/admin/admin-components/admin-posts/admin-posts.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { AngularFireDatabase, AngularFireList, AngularFireObject } from '@angular/fire/database';
3 | import * as firebase from 'firebase/app';
4 | import { FirebaseApp } from '@angular/fire';
5 | import { GlobalService } from '../../../services/global.service';
6 | import { Router } from '@angular/router';
7 | import { MatSnackBar } from '@angular/material/snack-bar';
8 | import { MatDialog, MatDialogRef } from '@angular/material/dialog';
9 | import { DeleteDialogComponent } from '../delete-dialog/delete-dialog.component'
10 | import { Observable } from 'rxjs';
11 |
12 | @Component({
13 | selector: 'admin-posts',
14 | templateUrl: './admin-posts.component.html',
15 | styleUrls: ['./admin-posts.component.scss']
16 | })
17 | export class AdminPostsComponent implements OnInit {
18 |
19 | posts: Observable;
20 | post: AngularFireObject;
21 | selectedOption: any;
22 | dialogRef: MatDialogRef;
23 | storageRef: any;
24 | currentAdmin: any;
25 |
26 | constructor(
27 | public af: FirebaseApp,
28 | public db: AngularFireDatabase,
29 | public globalService: GlobalService,
30 | public router: Router,
31 | public dialog: MatDialog,
32 | public snackBar: MatSnackBar
33 | ) {
34 | this.posts = db.list('/posts').snapshotChanges();
35 |
36 | this.storageRef = af.storage().ref();
37 |
38 | this.globalService.admin.subscribe((a) => {
39 | this.currentAdmin = a;
40 | });
41 | }
42 |
43 | onChange(e: any, key: string) {
44 | this.post = this.db.object('/posts/' + key);
45 | if (e.checked) {
46 | this.post.update({published: true});
47 | } else {
48 | this.post.update({published: false});
49 | }
50 | }
51 |
52 | deletePost(post: any) {
53 | let dialogRef = this.dialog.open(DeleteDialogComponent);
54 | dialogRef.afterClosed().subscribe(result => {
55 | this.selectedOption = result;
56 | if (this.selectedOption === 'delete') {
57 | this.db.object('/posts/' + post.key).remove();
58 |
59 | // if (post.thumbnail) {
60 | // let storage = firebase.storage();
61 | // let imageRef = storage.refFromURL(post.thumbnail);
62 | // let me = this;
63 | // imageRef.delete().then(function() {
64 | // let snackBarRef = me.snackBar.open('Post deleted', 'OK!', {
65 | // duration: 3000
66 | // });
67 | // }).catch(function(error) {
68 | // console.log('error', error);
69 | // });
70 | // } else {
71 | let snackBarRef = this.snackBar.open('Post deleted', 'OK!', {
72 | duration: 3000
73 | });
74 | // }
75 | }
76 | });
77 | }
78 |
79 | ngOnInit() {
80 | }
81 |
82 | }
83 |
--------------------------------------------------------------------------------
/src/app/admin/admin-components/admin-product-categories/admin-product-categories.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Product Categories
4 |
5 |
6 |
7 | No categories yet
8 |
9 |
10 |
17 |
18 |
19 |
22 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/src/app/admin/admin-components/admin-product-categories/admin-product-categories.component.scss:
--------------------------------------------------------------------------------
1 | @import '../../../../variables.scss';
2 |
3 | .button-container {
4 | h2 {
5 | display: inline-block;
6 | }
7 |
8 | button {
9 | float: right;
10 | margin: 18px 30px 30px 30px;
11 | }
12 | }
13 |
14 | .category {
15 | cursor: pointer;
16 |
17 | .content {
18 | display: inline-block;
19 | width: calc(100% - 410px);
20 |
21 | h3 {
22 | margin: 0 0 10px 0;
23 | }
24 | }
25 |
26 | .publish {
27 | float: right;
28 | text-align: right;
29 | vertical-align: top;
30 | width: 400px;
31 | }
32 | }
33 |
34 | .edit {
35 | display: inline-block;
36 | text-align: center;
37 | }
--------------------------------------------------------------------------------
/src/app/admin/admin-components/admin-product-categories/admin-product-categories.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { AdminProductCategoriesComponent } from './admin-product-categories.component';
4 |
5 | describe('AdminProductCategoriesComponent', () => {
6 | let component: AdminProductCategoriesComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async(() => {
10 | TestBed.configureTestingModule({
11 | declarations: [ AdminProductCategoriesComponent ]
12 | })
13 | .compileComponents();
14 | }));
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(AdminProductCategoriesComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should be created', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/src/app/admin/admin-components/admin-product-categories/admin-product-categories.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { AngularFireDatabase, AngularFireList } from '@angular/fire/database';
3 | import { Router } from '@angular/router';
4 | import { DeleteDialogComponent } from '../delete-dialog/delete-dialog.component';
5 | import { MatSnackBar } from '@angular/material/snack-bar';
6 | import { MatDialog, MatDialogRef } from '@angular/material/dialog';
7 | import { GlobalService } from '../../../services/global.service';
8 | import { Observable } from 'rxjs';
9 |
10 | @Component({
11 | selector: 'app-admin-product-categories',
12 | templateUrl: './admin-product-categories.component.html',
13 | styleUrls: ['./admin-product-categories.component.scss']
14 | })
15 | export class AdminProductCategoriesComponent implements OnInit {
16 |
17 | categories: Observable;
18 | dialogRef: MatDialogRef;
19 | selectedOption: any;
20 | currentAdmin: any;
21 |
22 | constructor(
23 | public db: AngularFireDatabase,
24 | public router: Router,
25 | public dialog: MatDialog,
26 | public snackBar: MatSnackBar,
27 | public globalService: GlobalService
28 | ) {
29 | this.categories = db.list('/categories').snapshotChanges();
30 |
31 | this.globalService.admin.subscribe((a) => {
32 | this.currentAdmin = a;
33 | });
34 | }
35 |
36 | deleteCategory(category: any) {
37 | let dialogRef = this.dialog.open(DeleteDialogComponent);
38 | dialogRef.afterClosed().subscribe(result => {
39 | this.selectedOption = result;
40 | if (this.selectedOption === 'delete') {
41 | this.db.object('/categories/' + category.key).remove();
42 |
43 | let snackBarRef = this.snackBar.open('Category deleted', 'OK!', {
44 | duration: 3000
45 | });
46 | }
47 | });
48 | }
49 |
50 | ngOnInit() {
51 | }
52 |
53 | }
54 |
--------------------------------------------------------------------------------
/src/app/admin/admin-components/admin-products/admin-products.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Products
4 |
5 |
6 |
7 | No products yet
8 |
9 |
10 |
11 |
12 |
{{product.payload.val().price | currency:'USD':true }}
13 |
14 |
15 |
16 | Publish
17 | Un-Publish
18 |
19 |
20 |
23 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/src/app/admin/admin-components/admin-products/admin-products.component.scss:
--------------------------------------------------------------------------------
1 | @import '../../../../variables.scss';
2 |
3 | .button-container {
4 | h2 {
5 | display: inline-block;
6 | }
7 |
8 | button {
9 | float: right;
10 | margin: 18px 30px 30px 30px;
11 | }
12 | }
13 |
14 | .product {
15 | cursor: pointer;
16 |
17 | .content {
18 | display: inline-block;
19 | width: calc(100% - 410px);
20 |
21 | h3 {
22 | margin: 0 0 10px 0;
23 | }
24 |
25 | .price {
26 | color: $color-grey;
27 | font-size: .8em;
28 | }
29 | }
30 |
31 | .publish {
32 | float: right;
33 | margin-top: 10px;
34 | text-align: right;
35 | vertical-align: top;
36 | width: 400px;
37 | }
38 | }
39 |
40 | .mat-slide-toggle {
41 | margin: 0 20px 0 45px;
42 | width: 130px;
43 | }
44 |
45 | .edit {
46 | display: inline-block;
47 | text-align: center;
48 | }
--------------------------------------------------------------------------------
/src/app/admin/admin-components/admin-products/admin-products.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { AdminProductsComponent } from './admin-products.component';
4 |
5 | describe('AdminProductsComponent', () => {
6 | let component: AdminProductsComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async(() => {
10 | TestBed.configureTestingModule({
11 | declarations: [ AdminProductsComponent ]
12 | })
13 | .compileComponents();
14 | }));
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(AdminProductsComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should be created', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/src/app/admin/admin-components/admin-theme/admin-theme.component.html:
--------------------------------------------------------------------------------
1 |
2 |
Theme
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/src/app/admin/admin-components/admin-theme/admin-theme.component.scss:
--------------------------------------------------------------------------------
1 | button {
2 | display: block;
3 | }
--------------------------------------------------------------------------------
/src/app/admin/admin-components/admin-theme/admin-theme.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { AdminThemeComponent } from './admin-theme.component';
4 |
5 | describe('AdminThemeComponent', () => {
6 | let component: AdminThemeComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async(() => {
10 | TestBed.configureTestingModule({
11 | declarations: [ AdminThemeComponent ]
12 | })
13 | .compileComponents();
14 | }));
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(AdminThemeComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should be created', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/src/app/admin/admin-components/admin-theme/admin-theme.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { AngularFireDatabase, AngularFireObject } from '@angular/fire/database';
3 | import { MatSnackBar } from '@angular/material/snack-bar';
4 |
5 | @Component({
6 | selector: 'admin-theme',
7 | templateUrl: './admin-theme.component.html',
8 | styleUrls: ['./admin-theme.component.scss']
9 | })
10 | export class AdminThemeComponent implements OnInit {
11 |
12 | theme: AngularFireObject;
13 | siteName: string;
14 |
15 | constructor(public db: AngularFireDatabase, public snackBar: MatSnackBar) {
16 | this.theme = db.object('/theme');
17 | }
18 |
19 | ngOnInit() {
20 | this.theme.valueChanges().subscribe((item:any) => {
21 | if (item && item.siteName) {
22 | this.siteName = item.siteName;
23 | }
24 | });
25 | }
26 |
27 | saveTheme(newSiteName: string) {
28 | this.theme.update({
29 | siteName: newSiteName
30 | });
31 |
32 | let snackBarRef = this.snackBar.open('Theme updated', 'OK!', {
33 | duration: 3000
34 | });
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/app/admin/admin-components/admin/admin.component.scss:
--------------------------------------------------------------------------------
1 | @import '../../../../variables.scss';
2 |
3 | .mat-sidenav-content {
4 | margin-top: -64px;
5 | }
6 |
7 | .admin-photo {
8 | border-radius: 50%;
9 | cursor: pointer;
10 | height: 45px;
11 | transition: transform .3s ease-out;
12 | vertical-align: middle;
13 | width: 45px;
14 |
15 | &:hover {
16 | transform: scale(1.1);
17 | }
18 | }
19 |
20 | mat-sidenav {
21 | box-shadow: 0 0 4px 2px rgba(0,0,0,.2);
22 | width: 300px;
23 | }
24 |
25 | .mat-sidenav-container {
26 | height: 100vh;
27 | }
28 |
29 | .admin-nav {
30 | mat-list-item {
31 | cursor: pointer;
32 | transition: background .2s ease-out;
33 |
34 | &:hover,
35 | &.selected {
36 | background: $color-grey-light;
37 | outline: 0;
38 | }
39 |
40 | .mat-icon {
41 | color: $color-main;
42 | margin-right: 10px;
43 | }
44 | }
45 | }
46 |
47 | .container {
48 | float: right;
49 | width: 77%;
50 | }
--------------------------------------------------------------------------------
/src/app/admin/admin-components/admin/admin.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { AdminComponent } from './admin.component';
4 |
5 | describe('AdminComponent', () => {
6 | let component: AdminComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async(() => {
10 | TestBed.configureTestingModule({
11 | declarations: [ AdminComponent ]
12 | })
13 | .compileComponents();
14 | }));
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(AdminComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should be created', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/src/app/admin/admin-components/admin/admin.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { Observable } from 'rxjs';
3 | import { Router } from '@angular/router';
4 | import { AngularFireAuth } from '@angular/fire/auth';
5 | import { AngularFireDatabase } from '@angular/fire/database';
6 | import * as firebase from 'firebase/app';
7 | import { MatSnackBar } from '@angular/material/snack-bar';
8 | import { GlobalService } from '../../../services/global.service';
9 |
10 | @Component({
11 | selector: 'admin',
12 | templateUrl: './admin.component.html',
13 | styleUrls: ['./admin.component.scss']
14 | })
15 |
16 | export class AdminComponent implements OnInit {
17 |
18 | user: Observable;
19 | currentAdmin: any;
20 |
21 | constructor(
22 | public db: AngularFireDatabase,
23 | public afAuth: AngularFireAuth,
24 | public router: Router,
25 | public globalService: GlobalService,
26 | public snackBar: MatSnackBar,
27 | ) {
28 | this.user = afAuth.authState;
29 | this.currentAdmin = {};
30 | }
31 |
32 | ngOnInit() {
33 | this.user.subscribe(currentUser => {
34 | if (!currentUser) {
35 | this.router.navigateByUrl('login');
36 | } else {
37 | this.db.object('/admins/' + this.globalService.hashCode(currentUser.email)).valueChanges().subscribe((a:any) => {
38 | if (a && a.email) {
39 | this.globalService.admin.next(currentUser);
40 | this.db.object('/admins/' + currentUser.uid).valueChanges().subscribe((a:any) => {
41 | this.globalService.admin.next(a);
42 | this.currentAdmin.role = a.role;
43 | });
44 | } else {
45 | this.router.navigateByUrl('');
46 | let snackBarRef = this.snackBar.open('You are not an authorized administrator', 'OK!', {
47 | duration: 3000
48 | });
49 | }
50 | }, (err) => {
51 | console.log('Permission Error', err);
52 | this.router.navigateByUrl('');
53 | let snackBarRef = this.snackBar.open('You are not an authorized administrator', 'OK!', {
54 | duration: 3000
55 | });
56 | });
57 | }
58 | });
59 | }
60 |
61 | logout() {
62 | this.afAuth.signOut();
63 | }
64 |
65 | }
66 |
--------------------------------------------------------------------------------
/src/app/admin/admin-components/approve-dialog/approve-dialog.component.html:
--------------------------------------------------------------------------------
1 | Are you sure?
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/app/admin/admin-components/approve-dialog/approve-dialog.component.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alexabbott/firebase-cms/9b2e0ebd86485a2937b05dae0c2d5c365daf0562/src/app/admin/admin-components/approve-dialog/approve-dialog.component.scss
--------------------------------------------------------------------------------
/src/app/admin/admin-components/approve-dialog/approve-dialog.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { ApproveDialogComponent } from './approve-dialog.component';
4 |
5 | describe('ApproveDialogComponent', () => {
6 | let component: ApproveDialogComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async(() => {
10 | TestBed.configureTestingModule({
11 | declarations: [ ApproveDialogComponent ]
12 | })
13 | .compileComponents();
14 | }));
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(ApproveDialogComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should be created', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/src/app/admin/admin-components/approve-dialog/approve-dialog.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { MatDialogRef } from '@angular/material/dialog';
3 |
4 | @Component({
5 | selector: 'approve-dialog',
6 | templateUrl: './approve-dialog.component.html',
7 | styleUrls: ['./approve-dialog.component.scss']
8 | })
9 | export class ApproveDialogComponent {
10 |
11 | constructor(public dialogRef: MatDialogRef) { }
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/src/app/admin/admin-components/delete-dialog/delete-dialog.component.html:
--------------------------------------------------------------------------------
1 | Are you sure?
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/app/admin/admin-components/delete-dialog/delete-dialog.component.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alexabbott/firebase-cms/9b2e0ebd86485a2937b05dae0c2d5c365daf0562/src/app/admin/admin-components/delete-dialog/delete-dialog.component.scss
--------------------------------------------------------------------------------
/src/app/admin/admin-components/delete-dialog/delete-dialog.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { DeleteDialogComponent } from './delete-dialog.component';
4 |
5 | describe('DeleteDialogComponent', () => {
6 | let component: DeleteDialogComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async(() => {
10 | TestBed.configureTestingModule({
11 | declarations: [ DeleteDialogComponent ]
12 | })
13 | .compileComponents();
14 | }));
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(DeleteDialogComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should create', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/src/app/admin/admin-components/delete-dialog/delete-dialog.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { MatDialog, MatDialogRef } from '@angular/material/dialog';
3 |
4 | @Component({
5 | selector: 'delete-dialog',
6 | templateUrl: './delete-dialog.component.html',
7 | styleUrls: ['./delete-dialog.component.scss']
8 | })
9 | export class DeleteDialogComponent {
10 |
11 | constructor(public dialogRef: MatDialogRef) { }
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/src/app/app.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | -
8 | {{item.label}}
9 | {{item.label}}
10 |
11 |
12 | -
13 | search
14 |
15 | -
16 |
17 |
18 | -
19 |
20 |
21 |
22 |
23 |
24 |
25 | -
26 |
27 |
28 |
29 |
30 |
31 |
32 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/src/app/app.component.scss:
--------------------------------------------------------------------------------
1 | @import '../variables.scss';
2 |
3 | .main-header {
4 | a {
5 | text-decoration: none;
6 | }
7 |
8 | h1 {
9 | font-weight: 800;
10 | margin-left: 11px;
11 |
12 | a {
13 | color: $color-main;
14 | }
15 | }
16 |
17 | mat-icon {
18 | cursor: pointer;
19 | display: inline-block;
20 |
21 | @include tablet {
22 | display: none;
23 | }
24 | }
25 |
26 | ul li {
27 | display: inline-block;
28 | font-size: 0.8em;
29 | list-style-type: none;
30 | margin-left: 25px;
31 |
32 | &.user-nav {
33 | margin-right: -30px;
34 |
35 | @include tablet {
36 | margin-right: 0;
37 | }
38 | }
39 |
40 | &.cart-nav {
41 | margin-left: -20px;
42 |
43 | @include tablet {
44 | margin-left: 15px;
45 | }
46 | }
47 |
48 | &.search-nav {
49 | margin-left: 15px;
50 | margin-right: 35px;
51 |
52 | @include tablet {
53 | margin-right: 0;
54 | }
55 |
56 | mat-icon {
57 | display: inline-block;
58 | transform: translateY(7px);
59 | }
60 | }
61 | }
62 |
63 | .nav-item {
64 | display: none;
65 |
66 | @include tablet {
67 | display: inline-block;
68 | }
69 | }
70 | }
71 |
72 | .fill-space {
73 | flex: 1 1 auto;
74 | }
75 |
76 | .user-photo {
77 | border-radius: 50%;
78 | cursor: pointer;
79 | vertical-align: middle;
80 | transition: transform .3s ease-out;
81 | width: 45px;
82 |
83 | &:hover {
84 | transform: scale(1.1);
85 | }
86 | }
87 |
88 | .mobile-nav {
89 | box-sizing: border-box;
90 | min-width: 50%;
91 | padding: 60px 20px 30px 20px;
92 |
93 | .mobile-nav-item {
94 | margin: 20px 0;
95 | }
96 | }
97 |
98 | .search {
99 | padding: 0px 20px;
100 | }
--------------------------------------------------------------------------------
/src/app/app.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed, async } from '@angular/core/testing';
2 |
3 | import { AppComponent } from './app.component';
4 |
5 | describe('AppComponent', () => {
6 | beforeEach(async(() => {
7 | TestBed.configureTestingModule({
8 | declarations: [
9 | AppComponent
10 | ],
11 | }).compileComponents();
12 | }));
13 |
14 | it('should create the app', async(() => {
15 | const fixture = TestBed.createComponent(AppComponent);
16 | const app = fixture.debugElement.componentInstance;
17 | expect(app).toBeTruthy();
18 | }));
19 |
20 | it(`should have as title 'app'`, async(() => {
21 | const fixture = TestBed.createComponent(AppComponent);
22 | const app = fixture.debugElement.componentInstance;
23 | expect(app.title).toEqual('app');
24 | }));
25 |
26 | it('should render title in a h1 tag', async(() => {
27 | const fixture = TestBed.createComponent(AppComponent);
28 | fixture.detectChanges();
29 | const compiled = fixture.debugElement.nativeElement;
30 | expect(compiled.querySelector('h1').textContent).toContain('Welcome to app!!');
31 | }));
32 | });
33 |
--------------------------------------------------------------------------------
/src/app/app.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { Observable } from 'rxjs';
3 | import { Router, ActivatedRoute } from '@angular/router';
4 | import { AngularFireDatabase, AngularFireList, AngularFireObject } from '@angular/fire/database';
5 | import { AngularFireAuth } from '@angular/fire/auth';
6 | import * as firebase from 'firebase/app';
7 | import { GlobalService } from './services/global.service';
8 | import { LocalCartService } from './services/localcart.service';
9 |
10 | @Component({
11 | selector: 'app-root',
12 | templateUrl: './app.component.html',
13 | styleUrls: ['./app.component.scss']
14 | })
15 | export class AppComponent {
16 | title = 'app';
17 | nav: Observable;
18 | theme: Observable;
19 | user: Observable;
20 |
21 | constructor(
22 | public router: Router,
23 | public route: ActivatedRoute,
24 | public db: AngularFireDatabase,
25 | public afAuth: AngularFireAuth,
26 | public globalService: GlobalService,
27 | public localCart: LocalCartService,
28 | ) {
29 | this.nav = db.list('/menus/nav').valueChanges();
30 | this.theme = db.object('/theme').valueChanges();
31 |
32 | this.user = afAuth.authState;
33 | this.user.subscribe(currentUser => {
34 | globalService.user.next(currentUser);
35 |
36 | if (currentUser) {
37 | this.db.object('/users/' + currentUser.uid).update({
38 | uid: currentUser.uid,
39 | email: currentUser.email,
40 | photoURL: currentUser.photoURL,
41 | status: 'active'
42 | });
43 |
44 | this.db.object('/users/' + currentUser.uid).valueChanges().subscribe((user:any) => {
45 | if (user.cart) {
46 | globalService.cart.next(user.cart);
47 | }
48 | });
49 | }
50 |
51 | if (!currentUser && this.localCart.cartHasItems()) {
52 | this.globalService.cart.next(this.localCart.cartGetItems());
53 | }
54 | });
55 | }
56 |
57 | login() {
58 | this.afAuth.signInWithPopup(new firebase.default.auth.GoogleAuthProvider());
59 | }
60 |
61 | logout() {
62 | this.globalService.cart.next(null);
63 | this.globalService.order.next(null);
64 | this.localCart.clearAll();
65 | this.afAuth.signOut();
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/app/directives/stop-propagation.directive.spec.ts:
--------------------------------------------------------------------------------
1 | import { StopPropagationDirective } from './stop-propagation.directive';
2 |
3 | describe('StopPropagationDirective', () => {
4 | it('should create an instance', () => {
5 | const directive = new StopPropagationDirective();
6 | expect(directive).toBeTruthy();
7 | });
8 | });
9 |
--------------------------------------------------------------------------------
/src/app/directives/stop-propagation.directive.ts:
--------------------------------------------------------------------------------
1 | import { Directive, HostListener } from '@angular/core';
2 |
3 | @Directive({
4 | selector: '[stop-propagation]'
5 | })
6 | export class StopPropagationDirective
7 | {
8 | @HostListener('click', ['$event'])
9 | public onClick(event: any): void
10 | {
11 | event.stopPropagation();
12 | }
13 | }
--------------------------------------------------------------------------------
/src/app/firebase.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { AngularFireModule } from '@angular/fire';
3 | import { AngularFireDatabaseModule } from '@angular/fire/database';
4 | import { AngularFireAuthModule } from '@angular/fire/auth';
5 | import { AngularFireStorageModule } from '@angular/fire/storage';
6 | import { AngularFirestoreModule } from '@angular/fire/firestore';
7 | import { AngularFireAuthGuardModule } from '@angular/fire/auth-guard';
8 | import * as firebase from 'firebase';
9 | import { environment } from './../environments/environment';
10 |
11 | firebase.default.initializeApp(environment.firebase);
12 |
13 | @NgModule({
14 | imports: [
15 | AngularFireModule.initializeApp(environment.firebase, 'firebase-cms'),
16 | AngularFirestoreModule,
17 | AngularFireDatabaseModule,
18 | AngularFireAuthModule,
19 | AngularFireStorageModule,
20 | AngularFireAuthGuardModule
21 | ],
22 | exports: [
23 | AngularFireModule,
24 | AngularFirestoreModule,
25 | AngularFireDatabaseModule,
26 | AngularFireAuthModule,
27 | AngularFireStorageModule,
28 | AngularFireAuthGuardModule
29 | ]
30 | })
31 | export class FirebaseModule { }
32 |
--------------------------------------------------------------------------------
/src/app/materialcomponents.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { CommonModule } from '@angular/common';
3 |
4 | import { MatNativeDateModule, MatOptionModule } from '@angular/material/core';
5 | import { MatButtonModule } from '@angular/material/button';
6 | import { MatCardModule } from '@angular/material/card';
7 | import { MatDatepickerModule } from '@angular/material/datepicker';
8 | import { MatDialogModule } from '@angular/material/dialog';
9 | import { MatCheckboxModule } from '@angular/material/checkbox';
10 | import { MatGridListModule } from '@angular/material/grid-list';
11 | import { MatIconModule } from '@angular/material/icon';
12 | import { MatInputModule } from '@angular/material/input';
13 | import { MatListModule } from '@angular/material/list';
14 | import { MatMenuModule } from '@angular/material/menu';
15 | import { MatSelectModule } from '@angular/material/select';
16 | import { MatSidenavModule } from '@angular/material/sidenav';
17 | import { MatSlideToggleModule } from '@angular/material/slide-toggle';
18 | import { MatSnackBarModule } from '@angular/material/snack-bar';
19 | import { MatToolbarModule } from '@angular/material/toolbar';
20 | import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
21 | import { MatBadgeModule } from '@angular/material/badge';
22 | import { MatTooltipModule } from '@angular/material/tooltip';
23 | import { MatChipsModule } from '@angular/material/chips';
24 | import { MatAutocompleteModule } from '@angular/material/autocomplete';
25 | import { MatTableModule } from '@angular/material/table';
26 | import { MatSortModule } from '@angular/material/sort';
27 | import { MatPaginatorModule } from '@angular/material/paginator';
28 | import { MatExpansionModule } from '@angular/material/expansion';
29 | import { MatTreeModule } from '@angular/material/tree';
30 |
31 |
32 | const components = [
33 | CommonModule,
34 | MatButtonModule,
35 | MatCardModule,
36 | MatDatepickerModule,
37 | MatDialogModule,
38 | MatCheckboxModule,
39 | MatGridListModule,
40 | MatIconModule,
41 | MatInputModule,
42 | MatListModule,
43 | MatMenuModule,
44 | MatNativeDateModule,
45 | MatOptionModule,
46 | MatSelectModule,
47 | MatSidenavModule,
48 | MatSlideToggleModule,
49 | MatSnackBarModule,
50 | MatToolbarModule,
51 | MatProgressSpinnerModule,
52 | MatBadgeModule,
53 | MatTooltipModule,
54 | MatChipsModule,
55 | MatAutocompleteModule,
56 | MatTableModule,
57 | MatSortModule,
58 | MatPaginatorModule,
59 | MatExpansionModule,
60 | MatTreeModule
61 | ];
62 |
63 | @NgModule({
64 | imports: components,
65 | exports: components
66 | })
67 |
68 | export class MaterialComponentsModule { }
69 |
--------------------------------------------------------------------------------
/src/app/pipes/get-key.pipe.spec.ts:
--------------------------------------------------------------------------------
1 | import { GetKeyPipe } from './get-key.pipe';
2 |
3 | describe('GetKeyPipe', () => {
4 | it('create an instance', () => {
5 | const pipe = new GetKeyPipe();
6 | expect(pipe).toBeTruthy();
7 | });
8 | });
9 |
--------------------------------------------------------------------------------
/src/app/pipes/get-key.pipe.ts:
--------------------------------------------------------------------------------
1 | import { Pipe, PipeTransform } from '@angular/core';
2 |
3 | @Pipe({ name: 'getKey' })
4 | export class GetKeyPipe implements PipeTransform {
5 | transform(val, args) {
6 | if (val === null || !val || !val[0]) return val;
7 | if (val) {
8 | return val.filter((v) => {
9 | return v.key === args;
10 | })[0];
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/app/pipes/getUser.pipe.spec.ts:
--------------------------------------------------------------------------------
1 | import { GetUserPipe } from './getUser.pipe';
2 |
3 | describe('GetUserPipe', () => {
4 | it('create an instance', () => {
5 | const pipe = new GetUserPipe();
6 | expect(pipe).toBeTruthy();
7 | });
8 | });
9 |
--------------------------------------------------------------------------------
/src/app/pipes/getUser.pipe.ts:
--------------------------------------------------------------------------------
1 | import { Pipe, PipeTransform } from '@angular/core';
2 |
3 | @Pipe({ name: 'getUser' })
4 | export class GetUserPipe implements PipeTransform {
5 | transform(val, args) {
6 | if (val === null || !val || !val[0]) return val;
7 | if (val) {
8 | return val.filter((v) => {
9 | return v.uid === args;
10 | })[0];
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/app/pipes/object-count.pipe.spec.ts:
--------------------------------------------------------------------------------
1 | import { ObjectCountPipe } from './object-count.pipe';
2 |
3 | describe('ObjectCountPipe', () => {
4 | it('create an instance', () => {
5 | const pipe = new ObjectCountPipe();
6 | expect(pipe).toBeTruthy();
7 | });
8 | });
9 |
--------------------------------------------------------------------------------
/src/app/pipes/object-count.pipe.ts:
--------------------------------------------------------------------------------
1 | import { Pipe, PipeTransform } from '@angular/core';
2 |
3 | @Pipe({
4 | name: 'objectCount'
5 | })
6 | export class ObjectCountPipe implements PipeTransform {
7 |
8 | transform(value: any, args?: any): any {
9 | if (value) {
10 | return Object.keys(value).length;
11 | } else {
12 | return 0;
13 | }
14 | }
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/src/app/pipes/safe-html.pipe.spec.ts:
--------------------------------------------------------------------------------
1 | import { SafeHtmlPipe } from './safe-html.pipe';
2 |
3 | describe('SafeHtmlPipe', () => {
4 | it('create an instance', () => {
5 | const pipe = new SafeHtmlPipe();
6 | expect(pipe).toBeTruthy();
7 | });
8 | });
9 |
--------------------------------------------------------------------------------
/src/app/pipes/safe-html.pipe.ts:
--------------------------------------------------------------------------------
1 | import { Pipe, PipeTransform } from '@angular/core';
2 | import { DomSanitizer } from '@angular/platform-browser';
3 |
4 | @Pipe({name: 'safeHtml'})
5 | export class SafeHtmlPipe implements PipeTransform {
6 | constructor(private sanitizer:DomSanitizer){}
7 |
8 | transform(html) {
9 | return this.sanitizer.bypassSecurityTrustHtml(html);
10 | }
11 | }
--------------------------------------------------------------------------------
/src/app/pipes/search.pipe.spec.ts:
--------------------------------------------------------------------------------
1 | import { SearchPipe } from './search.pipe';
2 |
3 | describe('SearchPipe', () => {
4 | it('create an instance', () => {
5 | const pipe = new SearchPipe();
6 | expect(pipe).toBeTruthy();
7 | });
8 | });
9 |
--------------------------------------------------------------------------------
/src/app/pipes/search.pipe.ts:
--------------------------------------------------------------------------------
1 | import { Pipe, PipeTransform, Injectable } from '@angular/core';
2 |
3 | @Pipe({
4 | name:'search',
5 | pure: false
6 | })
7 | @Injectable()
8 | export class SearchPipe implements PipeTransform {
9 |
10 | transform(items :any ,term :any): any {
11 | if (term === undefined || term === '') return items;
12 |
13 | if (items) {
14 | return items.filter((item) => {
15 | if (item.title && item.description && term) {
16 | return item.title.toLowerCase().includes(term.toLowerCase()) || item.description.toLowerCase().includes(term.toLowerCase());
17 | }
18 |
19 | if (item.title && item.body && term) {
20 | return item.title.toLowerCase().includes(term.toLowerCase()) || item.body.toLowerCase().includes(term.toLowerCase());
21 | }
22 | })
23 | }
24 | }
25 | }
--------------------------------------------------------------------------------
/src/app/pipes/sort.pipe.spec.ts:
--------------------------------------------------------------------------------
1 | import { SortPipe } from './sort.pipe';
2 |
3 | describe('SortPipe', () => {
4 | it('create an instance', () => {
5 | const pipe = new SortPipe();
6 | expect(pipe).toBeTruthy();
7 | });
8 | });
9 |
--------------------------------------------------------------------------------
/src/app/pipes/sort.pipe.ts:
--------------------------------------------------------------------------------
1 | import {Pipe, PipeTransform} from '@angular/core';
2 |
3 | @Pipe({name: 'sort', pure: false})
4 | export class SortPipe implements PipeTransform {
5 |
6 | static _orderByComparator(a:any, b:any):number{
7 | //Parse strings as numbers to compare properly
8 | if(parseFloat(a) < parseFloat(b)) return -1;
9 | if(parseFloat(a) > parseFloat(b)) return 1;
10 |
11 | return 0; //equal each other
12 | }
13 |
14 | transform(input:any, [config = '+']): any{
15 |
16 | if(!Array.isArray(input)) return input;
17 |
18 | if(!Array.isArray(config) || (Array.isArray(config) && config.length == 1)){
19 | var propertyToCheck:string = !Array.isArray(config) ? config : config[0];
20 | var desc = propertyToCheck.substr(0, 1) == '-';
21 |
22 | //Basic array
23 | if(!propertyToCheck || propertyToCheck == '-' || propertyToCheck == '+'){
24 | return !desc ? input.sort() : input.sort().reverse();
25 | }
26 | else {
27 | var property:string = propertyToCheck.substr(0, 1) == '+' || propertyToCheck.substr(0, 1) == '-'
28 | ? propertyToCheck.substr(1)
29 | : propertyToCheck;
30 |
31 | return input.sort(function(a:any,b:any){
32 | return !desc
33 | ? SortPipe._orderByComparator(a[property], b[property])
34 | : -SortPipe._orderByComparator(a[property], b[property]);
35 | });
36 | }
37 | }
38 | else {
39 | //Loop over property of the array in order and sort
40 | return input.sort(function(a:any,b:any){
41 | for(var i:number = 0; i < config.length; i++){
42 | var desc = config[i].substr(0, 1) == '-';
43 | var property = config[i].substr(0, 1) == '+' || config[i].substr(0, 1) == '-'
44 | ? config[i].substr(1)
45 | : config[i];
46 |
47 | var comparison = !desc
48 | ? SortPipe._orderByComparator(a[property], b[property])
49 | : -SortPipe._orderByComparator(a[property], b[property]);
50 |
51 | //Don't return 0 yet in case of needing to sort by next property
52 | if(comparison != 0) return comparison;
53 | }
54 |
55 | return 0; //equal each other
56 | });
57 | }
58 | }
59 | }
--------------------------------------------------------------------------------
/src/app/pipes/truncate.pipe.spec.ts:
--------------------------------------------------------------------------------
1 | import { TruncatePipe } from './truncate.pipe';
2 |
3 | describe('TruncatePipe', () => {
4 | it('create an instance', () => {
5 | const pipe = new TruncatePipe();
6 | expect(pipe).toBeTruthy();
7 | });
8 | });
9 |
--------------------------------------------------------------------------------
/src/app/pipes/truncate.pipe.ts:
--------------------------------------------------------------------------------
1 | import { Pipe, PipeTransform } from '@angular/core';
2 |
3 | @Pipe({
4 | name: 'truncate'
5 | })
6 | export class TruncatePipe implements PipeTransform {
7 | transform(value: string, args: string[]) : string {
8 | let limit = args.length > 0 ? parseInt(args[0], 100) : 100;
9 | let trail = args.length > 1 ? args[1] : '...';
10 |
11 | return value.length > limit ? value.substring(0, limit) + trail : value;
12 | }
13 | }
--------------------------------------------------------------------------------
/src/app/services/admin.guard.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed, async, inject } from '@angular/core/testing';
2 |
3 | import { AdminGuard } from './admin.guard';
4 |
5 | describe('AdminGuard', () => {
6 | beforeEach(() => {
7 | TestBed.configureTestingModule({
8 | providers: [AdminGuard]
9 | });
10 | });
11 |
12 | it('should ...', inject([AdminGuard], (guard: AdminGuard) => {
13 | expect(guard).toBeTruthy();
14 | }));
15 | });
16 |
--------------------------------------------------------------------------------
/src/app/services/admin.guard.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { Router, CanActivate } from '@angular/router';
3 | import { AngularFireDatabase } from '@angular/fire/database';
4 | import { GlobalService } from '../services/global.service';
5 | import { Observable } from 'rxjs';
6 |
7 | @Injectable()
8 | export class AdminGuard implements CanActivate {
9 | currentUser: any;
10 |
11 | constructor(
12 | public router: Router,
13 | public db: AngularFireDatabase,
14 | public globalService: GlobalService
15 | ) {
16 | this.currentUser = globalService.user.getValue();
17 | }
18 |
19 | canActivate(): Promise {
20 | return new Promise(Resolve => {
21 | if (this.currentUser) {
22 | this.db.list('/admins', ref => ref.orderByChild('email').equalTo(this.currentUser.email)).valueChanges()
23 | .subscribe((u:any) => {
24 | if (u[0].role === 'super-admin' || u[0].role === 'admin') {
25 | return Resolve(true);
26 | } else {
27 | this.router.navigate(['/admin']);
28 | return Resolve(false);
29 | }
30 | });
31 | }
32 | })
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/app/services/auth.guard.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed, async, inject } from '@angular/core/testing';
2 |
3 | import { AuthGuard } from './auth.guard';
4 |
5 | describe('AuthGuard', () => {
6 | beforeEach(() => {
7 | TestBed.configureTestingModule({
8 | providers: [AuthGuard]
9 | });
10 | });
11 |
12 | it('should ...', inject([AuthGuard], (guard: AuthGuard) => {
13 | expect(guard).toBeTruthy();
14 | }));
15 | });
16 |
--------------------------------------------------------------------------------
/src/app/services/auth.guard.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { Router, CanActivate } from '@angular/router';
3 | import { AngularFireAuth } from '@angular/fire/auth';
4 | import { Observable } from 'rxjs';
5 | import * as firebase from 'firebase/app';
6 |
7 | @Injectable()
8 | export class AuthGuard implements CanActivate {
9 | auth: Observable;
10 |
11 | constructor(
12 | public router: Router,
13 | public afAuth: AngularFireAuth,
14 | ) {
15 | this.auth = afAuth.authState;
16 | }
17 |
18 | canActivate(): Promise {
19 | return new Promise(Resolve => {
20 | this.auth.subscribe(state => {
21 | if (state) {
22 | return Resolve(true);
23 | } else {
24 | this.router.navigate(['/login']);
25 | return Resolve(false);
26 | }
27 | })
28 | })
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/app/services/global.service.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed, inject } from '@angular/core/testing';
2 |
3 | import { GlobalService } from './global.service';
4 |
5 | describe('GlobalService', () => {
6 | beforeEach(() => {
7 | TestBed.configureTestingModule({
8 | providers: [GlobalService]
9 | });
10 | });
11 |
12 | it('should be created', inject([GlobalService], (service: GlobalService) => {
13 | expect(service).toBeTruthy();
14 | }));
15 | });
16 |
--------------------------------------------------------------------------------
/src/app/services/localcart.service.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed, inject } from '@angular/core/testing';
2 |
3 | import { LocalCartService } from './localcart.service';
4 |
5 | describe('LocalCartService', () => {
6 | beforeEach(() => {
7 | TestBed.configureTestingModule({
8 | providers: [LocalCartService]
9 | });
10 | });
11 |
12 | it('should be created', inject([LocalCartService], (service: LocalCartService) => {
13 | expect(service).toBeTruthy();
14 | }));
15 | });
16 |
--------------------------------------------------------------------------------
/src/app/services/super-admin.guard.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed, async, inject } from '@angular/core/testing';
2 |
3 | import { SuperAdminGuard } from './super-admin.guard';
4 |
5 | describe('SuperAdminGuard', () => {
6 | beforeEach(() => {
7 | TestBed.configureTestingModule({
8 | providers: [SuperAdminGuard]
9 | });
10 | });
11 |
12 | it('should ...', inject([SuperAdminGuard], (guard: SuperAdminGuard) => {
13 | expect(guard).toBeTruthy();
14 | }));
15 | });
16 |
--------------------------------------------------------------------------------
/src/app/services/super-admin.guard.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { Router, CanActivate } from '@angular/router';
3 | import { AngularFireDatabase } from '@angular/fire/database';
4 | import { GlobalService } from '../services/global.service';
5 | import { Observable } from 'rxjs';
6 |
7 | @Injectable()
8 | export class SuperAdminGuard implements CanActivate {
9 | currentUser: any;
10 |
11 | constructor(
12 | public router: Router,
13 | public db: AngularFireDatabase,
14 | public globalService: GlobalService
15 | ) {
16 | this.currentUser = globalService.user.getValue();
17 | }
18 |
19 | canActivate(): Promise {
20 | return new Promise(Resolve => {
21 | if (this.currentUser) {
22 | this.db.list('/admins', ref => ref.orderByChild('email').equalTo(this.currentUser.email)).valueChanges()
23 | .subscribe((u:any) => {
24 | if (u[0].role === 'super-admin') {
25 | return Resolve(true);
26 | } else {
27 | this.router.navigate(['/admin']);
28 | return Resolve(false);
29 | }
30 | });
31 | }
32 | })
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/app/services/window-ref.service.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed, inject } from '@angular/core/testing';
2 |
3 | import { WindowRefService } from './window-ref.service';
4 |
5 | describe('WindowRefService', () => {
6 | beforeEach(() => {
7 | TestBed.configureTestingModule({
8 | providers: [WindowRefService]
9 | });
10 | });
11 |
12 | it('should be created', inject([WindowRefService], (service: WindowRefService) => {
13 | expect(service).toBeTruthy();
14 | }));
15 | });
16 |
--------------------------------------------------------------------------------
/src/app/services/window-ref.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 |
3 | function getWindow(): any {
4 | return window;
5 | }
6 |
7 | @Injectable()
8 | export class WindowRefService {
9 |
10 | get nativeWindow(): any {
11 | return getWindow();
12 | }
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/src/app/shared.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { CommonModule } from '@angular/common';
3 | import { Routes, RouterModule } from '@angular/router';
4 | import { MaterialComponentsModule } from './materialcomponents.module'
5 |
6 | // Components
7 | import { OrderComponent } from './storefront-components/order/order.component';
8 |
9 | // Pipes
10 | import { SortPipe } from './pipes/sort.pipe';
11 | import { SafeHtmlPipe } from './pipes/safe-html.pipe';
12 | import { TruncatePipe } from './pipes/truncate.pipe';
13 | import { GetUserPipe } from './pipes/getUser.pipe';
14 | import { ObjectCountPipe } from './pipes/object-count.pipe';
15 | import { GetKeyPipe } from './pipes/get-key.pipe';
16 | import { SearchPipe } from './pipes/search.pipe';
17 |
18 | const pipes = [
19 | GetUserPipe,
20 | GetKeyPipe,
21 | ObjectCountPipe,
22 | SafeHtmlPipe,
23 | SortPipe,
24 | TruncatePipe,
25 | SearchPipe
26 | ];
27 |
28 | @NgModule({
29 | imports: [
30 | CommonModule,
31 | RouterModule,
32 | MaterialComponentsModule
33 | ],
34 | declarations: [
35 | ...pipes,
36 | OrderComponent
37 | ],
38 | exports: [
39 | ...pipes,
40 | RouterModule
41 | ]
42 | })
43 | export class SharedModule { }
44 |
--------------------------------------------------------------------------------
/src/app/storefront-components/cart-icon/cart-icon.component.html:
--------------------------------------------------------------------------------
1 |
2 | shopping_cart
3 | 0">{{ cartItems }}
4 |
5 |
--------------------------------------------------------------------------------
/src/app/storefront-components/cart-icon/cart-icon.component.scss:
--------------------------------------------------------------------------------
1 | @import '../../../variables.scss';
2 |
3 | .cart-icon {
4 | @extend %transition;
5 | cursor: pointer;
6 | transform: translateY(8px);
7 | transition-property: color;
8 |
9 | @include tablet {
10 | transform: translateY(5px);
11 | }
12 |
13 | &:hover {
14 | color: $color-main;
15 | }
16 | }
17 |
18 | .icon-container {
19 | display: inline-block;
20 | position: relative;
21 | }
22 |
23 | .cart-counter {
24 | bottom: 2px;
25 | color: $color-white;
26 | font-size: 8px;
27 | pointer-events: none;
28 | position: absolute;
29 | right: 9px;
30 |
31 | @include tablet {
32 | bottom: 5px;
33 | }
34 | }
--------------------------------------------------------------------------------
/src/app/storefront-components/cart-icon/cart-icon.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { CartIconComponent } from './cart-icon.component';
4 |
5 | describe('CartIconComponent', () => {
6 | let component: CartIconComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async(() => {
10 | TestBed.configureTestingModule({
11 | declarations: [ CartIconComponent ]
12 | })
13 | .compileComponents();
14 | }));
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(CartIconComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should be created', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/src/app/storefront-components/cart-icon/cart-icon.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { Observable } from 'rxjs';
3 | import { GlobalService } from '../../services/global.service';
4 | import { AngularFireDatabase } from '@angular/fire/database';
5 | import { AngularFireAuth } from '@angular/fire/auth';
6 | import * as firebase from 'firebase/app';
7 |
8 | @Component({
9 | selector: 'cart-icon',
10 | templateUrl: './cart-icon.component.html',
11 | styleUrls: ['./cart-icon.component.scss']
12 | })
13 | export class CartIconComponent implements OnInit {
14 | globalCart: any;
15 | user: Observable;
16 | cartItems = 0;
17 |
18 | constructor(public globalService: GlobalService, public afAuth: AngularFireAuth, public db: AngularFireDatabase) {
19 | this.user = afAuth.authState;
20 |
21 | globalService.cart.subscribe((cart) => {
22 | this.globalCart = cart;
23 |
24 | if (this.globalCart) {
25 | const cartArray = (Object).values(this.globalCart);
26 | this.cartItems = cartArray.reduce((sum, cartItem) => sum + cartItem.quantity, 0);
27 | } else {
28 | this.cartItems = 0;
29 | }
30 |
31 | this.user.subscribe(currentUser => {
32 | if (currentUser && currentUser.uid && cart && Object.keys(cart).length > 0) {
33 | db.object('/users/' + currentUser.uid).update({
34 | cart: cart
35 | });
36 | }
37 | });
38 | });
39 |
40 | }
41 |
42 | ngOnInit() {
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/app/storefront-components/cart/cart.component.html:
--------------------------------------------------------------------------------
1 |
2 |
Cart
3 |
9 |
10 |
11 |
12 |
![{{item.title}}]()
13 |

14 | {{item.title}}
15 |
16 | {{item.price | currency:'USD':true}}
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | {{item.total | currency:'USD':true}}
25 |
26 |
27 |
28 |
29 |
0 && !review">
30 | Subtotal
31 | {{cartTotal | currency:'USD':true}}
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/src/app/storefront-components/cart/cart.component.scss:
--------------------------------------------------------------------------------
1 | @import '../../../variables.scss';
2 |
3 | h3,
4 | .no-items {
5 | text-align: center;
6 | }
7 |
8 | .cart-header {
9 | display: none;
10 | margin-bottom: 10px;
11 |
12 | @include tablet {
13 | display: block;
14 | }
15 |
16 | .quantity {
17 | @include tablet {
18 | transform: none;
19 | }
20 | }
21 | }
22 |
23 | .product {
24 | min-height: 30px;
25 | padding: 24px 16px;
26 | }
27 |
28 | .item {
29 | display: block;
30 | width: 100%;
31 |
32 | @include tablet {
33 | float: left;
34 | width: 35%;
35 | }
36 |
37 | img {
38 | display: block;
39 | margin: 0 auto;
40 | max-width: 70%;
41 |
42 | @include tablet {
43 | display: inline-block;
44 | height: 50px;
45 | margin-top: -8px;
46 | object-fit: cover;
47 | vertical-align: middle;
48 | width: 50px;
49 | }
50 | }
51 | }
52 |
53 | .price,
54 | .quantity,
55 | .total {
56 | display: inline-block;
57 | width: 45%;
58 |
59 | @include tablet {
60 | float: left;
61 | width: 15%;
62 | }
63 | }
64 |
65 | .price {
66 | @include tablet {
67 | width: 20%;
68 | }
69 | }
70 |
71 | .total {
72 | display: none;
73 |
74 | @include tablet {
75 | display: inline-block;
76 | }
77 | }
78 |
79 | .quantity {
80 | position: relative;
81 | text-align: right;
82 |
83 | @include tablet {
84 | text-align: left;
85 | transform: translateY(-22px);
86 | }
87 |
88 | .quantity-label {
89 | color: $color-grey;
90 | display: inline-block;
91 | font-size: .8em;
92 | position: absolute;
93 | right: 40px;
94 | top: 0px;
95 |
96 | @include tablet {
97 | display: none;
98 | }
99 | }
100 |
101 | .mat-input-container {
102 | width: 35px;
103 | }
104 | }
105 |
106 | .remove {
107 | display: block;
108 | text-align: center;
109 | width: 100%;
110 |
111 | @include tablet {
112 | display: inline-block;
113 | text-align: left;
114 | width: 5%;
115 | }
116 | }
117 |
118 | .checkout-cta {
119 | box-sizing: border-box;
120 | float: right;
121 | margin: 20px 0 30px 0;
122 | width: 100%;
123 |
124 | @include tablet {
125 | width: 330px;
126 | }
127 |
128 | button {
129 | display: block;
130 | float: right;
131 | }
132 | }
133 |
134 | .subtotal-label,
135 | .subtotal {
136 | display: inline-block;
137 | width: 48%;
138 | }
139 |
140 | .subtotal {
141 | text-align: right;
142 | }
143 |
144 | .hide {
145 | display: none;
146 | }
147 |
148 | .no-padding {
149 | padding: 20px 0 0 0;
150 | }
--------------------------------------------------------------------------------
/src/app/storefront-components/cart/cart.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { CartComponent } from './cart.component';
4 |
5 | describe('CartComponent', () => {
6 | let component: CartComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async(() => {
10 | TestBed.configureTestingModule({
11 | declarations: [ CartComponent ]
12 | })
13 | .compileComponents();
14 | }));
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(CartComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should be created', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/src/app/storefront-components/checkout-billing/checkout-billing.component.html:
--------------------------------------------------------------------------------
1 |
2 | Billing info
3 | same as shipping
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | {{ state.name }}
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/src/app/storefront-components/checkout-billing/checkout-billing.component.scss:
--------------------------------------------------------------------------------
1 | @import '../../../variables.scss';
2 |
3 | .container {
4 | margin: 10px 30px 30px 30px;
5 | max-width: 100%;
6 |
7 | @include tablet {
8 | margin: 30px auto;
9 | max-width: 600px;
10 | }
11 | }
12 |
13 | h3 {
14 | display: inline-block;
15 | width: 50%;
16 | }
17 |
18 | .mat-checkbox {
19 | display: block;
20 | margin-bottom: 10px;
21 |
22 | @include tablet {
23 | display: inline-block;
24 | float: right;
25 | margin-bottom: 0;
26 | }
27 | }
28 |
29 | .mat-input-container,
30 | .mat-select {
31 | display: block;
32 | }
33 |
34 | .mat-select {
35 | margin: 20px auto 21px auto;
36 | }
--------------------------------------------------------------------------------
/src/app/storefront-components/checkout-billing/checkout-billing.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { CheckoutBillingComponent } from './checkout-billing.component';
4 |
5 | describe('CheckoutBillingComponent', () => {
6 | let component: CheckoutBillingComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async(() => {
10 | TestBed.configureTestingModule({
11 | declarations: [ CheckoutBillingComponent ]
12 | })
13 | .compileComponents();
14 | }));
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(CheckoutBillingComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should be created', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/src/app/storefront-components/checkout-billing/checkout-billing.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { Title, Meta } from '@angular/platform-browser';
3 | import { Router } from '@angular/router';
4 | import { MatSnackBar } from '@angular/material/snack-bar';
5 | import { LocalCartService } from '../../services/localcart.service';
6 | import { GlobalService } from '../../services/global.service';
7 |
8 |
9 | @Component({
10 | selector: 'checkout-billing',
11 | templateUrl: './checkout-billing.component.html',
12 | styleUrls: ['./checkout-billing.component.scss']
13 | })
14 | export class CheckoutBillingComponent implements OnInit {
15 | order: any;
16 | states: any;
17 | sameAsShipping: boolean;
18 |
19 | constructor(
20 | public snackBar: MatSnackBar,
21 | public router: Router,
22 | public globalService: GlobalService,
23 | public localCart: LocalCartService,
24 | private title: Title,
25 | private meta: Meta
26 | ) {
27 | this.states = globalService.states;
28 | this.order = globalService.order.getValue();
29 | if (!this.order.shipping) {
30 | router.navigateByUrl('cart');
31 | }
32 | if (this.localCart.orderHasItems() && this.localCart.orderHas('billing')) {
33 | this.order = this.localCart.orderGetItems();
34 | }
35 | }
36 |
37 | ngOnInit() {
38 | this.title.setTitle('Billing');
39 | this.meta.updateTag({ content: 'Billing info for the order' }, "name='description'");
40 |
41 | if (!this.order || this.order === {}) {
42 | this.router.navigateByUrl('checkout/shipping');
43 | }
44 | }
45 |
46 | copyShipping() {
47 | if (this.sameAsShipping) {
48 | this.order.billing = this.order.shipping;
49 | } else {
50 | this.order.billing = {};
51 | }
52 | }
53 |
54 | goTo(url: string) {
55 | if (this.order.billing.name &&
56 | this.order.billing.email &&
57 | this.order.billing.address &&
58 | this.order.billing.city &&
59 | this.order.billing.state &&
60 | this.order.billing.zip) {
61 | this.globalService.order.next(this.order);
62 | this.router.navigateByUrl(url);
63 | this.localCart.orderUpdateItems(this.order);
64 | } else {
65 | let snackBarRef = this.snackBar.open('You must complete the form', 'OK!', {
66 | duration: 3000,
67 | panelClass: ['warn-snackbar']
68 | });
69 | }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/app/storefront-components/checkout-confirmation/checkout-confirmation.component.html:
--------------------------------------------------------------------------------
1 |
2 | Order Confirmation
3 | Thank you for submitting your order!
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/app/storefront-components/checkout-confirmation/checkout-confirmation.component.scss:
--------------------------------------------------------------------------------
1 | @import '../../../variables.scss';
2 |
3 | .container {
4 | margin: 10px 30px 30px 30px;
5 | max-width: 100%;
6 |
7 | @include tablet {
8 | margin: 30px auto;
9 | max-width: 600px;
10 | }
11 | }
--------------------------------------------------------------------------------
/src/app/storefront-components/checkout-confirmation/checkout-confirmation.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { CheckoutConfirmationComponent } from './checkout-confirmation.component';
4 |
5 | describe('CheckoutConfirmationComponent', () => {
6 | let component: CheckoutConfirmationComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async(() => {
10 | TestBed.configureTestingModule({
11 | declarations: [ CheckoutConfirmationComponent ]
12 | })
13 | .compileComponents();
14 | }));
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(CheckoutConfirmationComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should be created', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/src/app/storefront-components/checkout-confirmation/checkout-confirmation.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { Title, Meta } from '@angular/platform-browser';
3 | import { Router } from '@angular/router';
4 | import { GlobalService } from '../../services/global.service';
5 |
6 | @Component({
7 | selector: 'app-checkout-confirmation',
8 | templateUrl: './checkout-confirmation.component.html',
9 | styleUrls: ['./checkout-confirmation.component.scss']
10 | })
11 | export class CheckoutConfirmationComponent implements OnInit {
12 | order: any;
13 | user: any;
14 |
15 | constructor(
16 | public router: Router,
17 | public globalService: GlobalService,
18 | private title: Title,
19 | private meta: Meta
20 | ) {
21 | this.order = globalService.order.getValue();
22 | this.user = globalService.user.getValue();
23 | }
24 |
25 | ngOnInit() {
26 | this.title.setTitle('Confirmation');
27 | this.meta.updateTag({ content: 'Order confirmation information' }, "name='description'");
28 | }
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/src/app/storefront-components/checkout-payment/checkout-payment.component.html:
--------------------------------------------------------------------------------
1 |
2 | Payment Info
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | /
18 |
19 |
20 |
21 |
22 |
23 |
24 | {{ newCreditCard.error }}
25 |
26 |
--------------------------------------------------------------------------------
/src/app/storefront-components/checkout-payment/checkout-payment.component.scss:
--------------------------------------------------------------------------------
1 | @import '../../../variables.scss';
2 |
3 | .container {
4 | margin: 10px 30px 30px 30px;
5 | max-width: 100%;
6 |
7 | @include tablet {
8 | margin: 30px auto;
9 | max-width: 600px;
10 | }
11 | }
--------------------------------------------------------------------------------
/src/app/storefront-components/checkout-payment/checkout-payment.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { CheckoutPaymentComponent } from './checkout-payment.component';
4 |
5 | describe('CheckoutPaymentComponent', () => {
6 | let component: CheckoutPaymentComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async(() => {
10 | TestBed.configureTestingModule({
11 | declarations: [ CheckoutPaymentComponent ]
12 | })
13 | .compileComponents();
14 | }));
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(CheckoutPaymentComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should be created', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/src/app/storefront-components/checkout-review/checkout-review.component.html:
--------------------------------------------------------------------------------
1 |
2 | Review Order
3 |
4 | Shipping Info
5 | {{ order.shipping.name }}
6 | {{ order.shipping.email }}
7 | {{ order.shipping.company }}
8 | {{ order.shipping.address }}
9 | {{ order.shipping.city }}, {{ order.shipping.state }}, {{ order.shipping.zip }}
10 |
11 | Billing Info
12 | {{ order.billing.name }}
13 | {{ order.billing.email }}
14 | {{ order.billing.company }}
15 | {{ order.billing.address }}
16 | {{ order.billing.city }}, {{ order.billing.state }}, {{ order.billing.zip }}
17 |
18 | Payment Info
19 |
20 |
21 | {{ newCharge.source.monbrand }} …{{ newCharge.source.last4 }}
22 | (exp. {{ newCharge.source.exp_th }}/{{ newCharge.source.exp_year }})
23 |
24 |
25 | Verifying payment method
26 |
27 |
28 |
29 |
30 | Edit Cart
31 |
32 | Subtotal: {{ order.total | currency:'USD':true }}
33 |
34 |
35 |
36 | {{ newCharge.error }}
37 |
38 |
--------------------------------------------------------------------------------
/src/app/storefront-components/checkout-review/checkout-review.component.scss:
--------------------------------------------------------------------------------
1 | @import '../../../variables.scss';
2 |
3 | .container {
4 | margin: 10px 30px 30px 30px;
5 | max-width: 100%;
6 |
7 | @include tablet {
8 | margin: 30px auto;
9 | max-width: 600px;
10 | }
11 | }
12 |
13 | h4 {
14 | margin-bottom: 5px;
15 | }
16 |
17 | .edit {
18 | float: right;
19 | }
20 |
21 | .confirm {
22 | display: block;
23 | margin-top: 40px;
24 | }
25 |
26 | .mat-progress-spinner {
27 | display: inline-block;
28 | height: 30px;
29 | vertical-align: middle;
30 | width: 30px;
31 | }
32 |
33 | .verify-card {
34 | color: $color-main;
35 | display: inline-block;
36 | font-size: .9em;
37 | margin-left: 10px;
38 | vertical-align: middle;
39 | }
--------------------------------------------------------------------------------
/src/app/storefront-components/checkout-review/checkout-review.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { CheckoutReviewComponent } from './checkout-review.component';
4 |
5 | describe('CheckoutReviewComponent', () => {
6 | let component: CheckoutReviewComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async(() => {
10 | TestBed.configureTestingModule({
11 | declarations: [ CheckoutReviewComponent ]
12 | })
13 | .compileComponents();
14 | }));
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(CheckoutReviewComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should be created', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/src/app/storefront-components/checkout-shipping/checkout-shipping.component.html:
--------------------------------------------------------------------------------
1 |
2 | Shipping Info
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | {{ state.name }}
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/src/app/storefront-components/checkout-shipping/checkout-shipping.component.scss:
--------------------------------------------------------------------------------
1 | @import '../../../variables.scss';
2 |
3 | .container {
4 | margin: 10px 30px 30px 30px;
5 | max-width: 100%;
6 |
7 | @include tablet {
8 | margin: 30px auto;
9 | max-width: 600px;
10 | }
11 | }
12 |
13 | .mat-input-container,
14 | .mat-select {
15 | display: block;
16 | }
17 |
18 | .mat-select {
19 | margin: 20px auto 21px auto;
20 | }
--------------------------------------------------------------------------------
/src/app/storefront-components/checkout-shipping/checkout-shipping.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { CheckoutShippingComponent } from './checkout-shipping.component';
4 |
5 | describe('CheckoutShippingComponent', () => {
6 | let component: CheckoutShippingComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async(() => {
10 | TestBed.configureTestingModule({
11 | declarations: [ CheckoutShippingComponent ]
12 | })
13 | .compileComponents();
14 | }));
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(CheckoutShippingComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should be created', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/src/app/storefront-components/checkout-shipping/checkout-shipping.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { Title, Meta } from '@angular/platform-browser';
3 | import { Router } from '@angular/router';
4 | import { MatSnackBar } from '@angular/material/snack-bar';
5 | import { GlobalService } from '../../services/global.service';
6 | import { LocalCartService } from '../../services/localcart.service';
7 |
8 | @Component({
9 | selector: 'checkout-shipping',
10 | templateUrl: './checkout-shipping.component.html',
11 | styleUrls: ['./checkout-shipping.component.scss']
12 | })
13 | export class CheckoutShippingComponent implements OnInit {
14 | order: any;
15 | states: any;
16 |
17 | constructor(
18 | public snackBar: MatSnackBar,
19 | public router: Router,
20 | public globalService: GlobalService,
21 | public localCart: LocalCartService,
22 | private title: Title,
23 | private meta: Meta
24 | ) {
25 | this.states = globalService.states;
26 | this.order = globalService.order.getValue();
27 | if (!this.order.items) {
28 | router.navigateByUrl('cart');
29 | }
30 | if (this.localCart.orderHasItems() && this.localCart.orderHas('shipping')) {
31 | this.order = this.localCart.orderGetItems();
32 | }
33 | }
34 |
35 | ngOnInit() {
36 | this.title.setTitle('Shipping');
37 | this.meta.addTag({ name: 'description', content: 'Enter shipping information for the order' });
38 | this.meta.updateTag({ content: 'Enter shipping information for the order' }, "name='description'");
39 | }
40 |
41 | goTo(url: string) {
42 | if (this.order.shipping.name &&
43 | this.order.shipping.email &&
44 | this.order.shipping.address &&
45 | this.order.shipping.city &&
46 | this.order.shipping.state &&
47 | this.order.shipping.zip) {
48 | this.globalService.order.next(this.order);
49 | this.router.navigateByUrl(url);
50 | this.localCart.orderUpdateItems(this.order);
51 | } else {
52 | let snackBarRef = this.snackBar.open('You must complete the form', 'OK!', {
53 | duration: 3000,
54 | panelClass: ['warn-snackbar']
55 | });
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/app/storefront-components/login/login.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
or
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
20 |
21 |
22 |
23 | Don't have an account?
Sign up
24 |
25 |
26 |
29 |
30 |
31 |
32 | Already have an account?
Log in
33 |
34 |
35 | You must be an approved admin to access the admin panel
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/src/app/storefront-components/login/login.component.scss:
--------------------------------------------------------------------------------
1 | .login-frame {
2 | align-items: center;
3 | display: flex;
4 | flex-direction: column;
5 | height: 100vh;
6 | justify-content: center;
7 | margin-top: -10vh;
8 | }
9 |
10 | .block {
11 | display: block;
12 | }
13 |
14 | .sign-up-trigger {
15 | margin-top: 20px;
16 | text-align: center;
17 |
18 | a {
19 | cursor: pointer;
20 | text-decoration: underline;
21 | }
22 | }
23 |
24 | button {
25 | margin-bottom: 20px;
26 | max-width: 100%;
27 | width: 100%;
28 |
29 | &.logout {
30 | max-width: 200px;
31 | }
32 | }
33 |
34 | button img {
35 | margin-right: 10px;
36 | vertical-align: middle;
37 | width: 20px;
38 | }
--------------------------------------------------------------------------------
/src/app/storefront-components/login/login.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { LoginComponent } from './login.component';
4 |
5 | describe('LoginComponent', () => {
6 | let component: LoginComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async(() => {
10 | TestBed.configureTestingModule({
11 | declarations: [ LoginComponent ]
12 | })
13 | .compileComponents();
14 | }));
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(LoginComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should be created', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/src/app/storefront-components/order/order.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Order #{{ order.key }}
4 |
{{ order.date | date }}
5 |
6 |
13 |
14 |
15 | Shipping info
16 | {{ order.shipping.name }}
17 | {{ order.shipping.company }}
18 | {{ order.shipping.email }}
19 | {{ order.shipping.address }}
20 | {{ order.shipping.city }}, {{ order.shipping.state }} {{ order.shipping.zip }}
21 |
22 |
23 | Billing info
24 | {{ order.billing.name }}
25 | {{ order.billing.company }}
26 | {{ order.billing.email }}
27 | {{ order.billing.address }}
28 | {{ order.billing.city }}, {{ order.billing.state }} {{ order.billing.zip }}
29 |
30 |
31 |
37 |
38 |
39 | {{item.title}}
40 | {{item.price | currency:'USD':true }}
41 |
42 | {{item.quantity}}
43 |
44 | {{item.total | currency:'USD':true }}
45 |
46 |
47 |
48 |
49 | Subtotal
50 | {{order.total | currency:'USD':true}}
51 |
52 |
--------------------------------------------------------------------------------
/src/app/storefront-components/order/order.component.scss:
--------------------------------------------------------------------------------
1 | @import '../../../variables.scss';
2 |
3 | .order-container {
4 | margin: 0;
5 | text-align: left;
6 | }
7 |
8 | .order-date {
9 | text-align: left;
10 | }
11 |
12 | .order-left {
13 | display: inline-block;
14 | text-align: left;
15 | vertical-align: top;
16 | width: 100%;
17 |
18 | @include tablet {
19 | width: 49%;
20 | }
21 | }
22 |
23 | .order-right {
24 | display: inline-block;
25 | text-align: right;
26 | vertical-align: top;
27 | width: 100%;
28 |
29 | @include tablet {
30 | width: 49%;
31 | }
32 | }
33 |
34 | .user-photo {
35 | border-radius: 50%;
36 | margin-right: 10px;
37 | vertical-align: middle;
38 | width: 40px;
39 | }
40 |
41 | .product {
42 | height: 27px;
43 | padding: 24px 16px;
44 | }
45 |
46 | .item {
47 | display: inline-block;
48 | width: 30%;
49 |
50 | @include tablet {
51 | float: left;
52 | width: 50%;
53 | }
54 | }
55 |
56 | .price,
57 | .quantity,
58 | .total {
59 | display: inline-block;
60 | width: 20%;
61 |
62 | @include tablet {
63 | float: left;
64 | width: 15%;
65 | }
66 | }
67 |
68 | .quantity {
69 | span {
70 | @include tablet {
71 | margin-left: 13px;
72 | }
73 | }
74 | }
75 |
76 | .order-summary {
77 | box-sizing: border-box;
78 | margin: 20px 0;
79 | width: 100%;
80 |
81 | @include tablet {
82 | float: right;
83 | width: 330px;
84 | }
85 | }
86 |
87 | .subtotal-label,
88 | .subtotal {
89 | display: inline-block;
90 | width: 48%;
91 | }
92 |
93 | .subtotal {
94 | text-align: right;
95 | }
96 |
97 | .mat-raised-button {
98 | margin-top: 20px;
99 | }
100 |
101 | .order-info {
102 | margin-top: 30px;
103 | text-align: left;
104 |
105 | @include tablet {
106 | display: flex;
107 | flex-direction: row;
108 | }
109 |
110 | .mat-card {
111 | box-sizing: border-box;
112 | display: inline-block;
113 | width: 100%;
114 | }
115 |
116 | .mat-card:last-of-type {
117 |
118 | @include tablet {
119 | margin-left: 30px;
120 | }
121 | }
122 | }
--------------------------------------------------------------------------------
/src/app/storefront-components/order/order.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { OrderComponent } from './order.component';
4 |
5 | describe('OrderComponent', () => {
6 | let component: OrderComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async(() => {
10 | TestBed.configureTestingModule({
11 | declarations: [ OrderComponent ]
12 | })
13 | .compileComponents();
14 | }));
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(OrderComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should be created', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/src/app/storefront-components/order/order.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { Title, Meta } from '@angular/platform-browser';
3 | import { ActivatedRoute, Params, Router } from '@angular/router';
4 | import { AngularFireDatabase } from '@angular/fire/database';
5 | import { Observable } from 'rxjs';
6 |
7 | @Component({
8 | selector: 'order',
9 | templateUrl: './order.component.html',
10 | styleUrls: ['./order.component.scss']
11 | })
12 | export class OrderComponent implements OnInit {
13 | orderContent: any;
14 | order: any;
15 | admin: boolean;
16 | customers: Observable;
17 |
18 | constructor(
19 | public db: AngularFireDatabase,
20 | public route: ActivatedRoute,
21 | public router: Router,
22 | private title: Title,
23 | private meta: Meta
24 | ) {
25 | this.admin = false;
26 | this.customers = db.list('/users').valueChanges();
27 | }
28 |
29 | ngOnInit() {
30 | if (this.router.url.includes('admin')) {
31 | this.admin = true;
32 | }
33 |
34 | this.route.params.subscribe((params: Params) => {
35 | this.title.setTitle('Order #' + params.key);
36 | this.meta.updateTag({ content: 'View the order dtails' }, "name='description'");
37 |
38 | this.orderContent = this.db.object('/orders/' + params.key);
39 | this.orderContent.valueChanges().subscribe((o) => {
40 | if (o) {
41 | this.order = o;
42 | this.order.key = params.key;
43 | } else {
44 | this.order = {
45 | title: 'Order Not Found',
46 | body: ''
47 | }
48 | }
49 | });
50 | });
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/app/storefront-components/orders/orders.component.html:
--------------------------------------------------------------------------------
1 |
2 |
My Orders
3 |
4 | Guest Customer
5 |
6 |
7 |
8 |
9 |
12 |
{{ orderDates[i] | date }}
13 |
14 |
15 |
16 |
17 |
18 |
19 |
No orders
20 |
--------------------------------------------------------------------------------
/src/app/storefront-components/orders/orders.component.scss:
--------------------------------------------------------------------------------
1 | @import '../../../variables.scss';
2 |
3 | .order {
4 | cursor: pointer;
5 |
6 | .order-info {
7 | display: inline-block;
8 | width: 100%;
9 |
10 | @include tablet {
11 | width: calc(100% - 220px);
12 | }
13 |
14 | h3 {
15 | margin-top: 0;
16 | }
17 | }
18 |
19 | .actions {
20 | text-align: right;
21 | width: 100%;
22 |
23 | @include tablet {
24 | display: inline-block;
25 | float: none;
26 | text-align: center;
27 | vertical-align: top;
28 | width: 210px;
29 | }
30 | }
31 |
32 | .totals {
33 | float: right;
34 | }
35 |
36 | .mat-raised-button {
37 | margin-top: 17px;
38 | }
39 | }
40 |
41 | .user-photo {
42 | border-radius: 50%;
43 | height: 50px;
44 | margin-right: 10px;
45 | vertical-align: middle;
46 | width: 50px;
47 | }
48 |
49 | .price {
50 | color: $color-grey;
51 | font-size: 0.8em;
52 | }
--------------------------------------------------------------------------------
/src/app/storefront-components/orders/orders.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { OrdersComponent } from './orders.component';
4 |
5 | describe('OrdersComponent', () => {
6 | let component: OrdersComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async(() => {
10 | TestBed.configureTestingModule({
11 | declarations: [ OrdersComponent ]
12 | })
13 | .compileComponents();
14 | }));
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(OrdersComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should be created', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/src/app/storefront-components/orders/orders.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { ActivatedRoute, Params, Router } from '@angular/router';
3 | import { Title, Meta } from '@angular/platform-browser';
4 | import { Observable } from 'rxjs';
5 | import { AngularFireDatabase } from '@angular/fire/database';
6 | import { AngularFireAuth } from '@angular/fire/auth';
7 | import * as firebase from 'firebase/app';
8 | import { GlobalService } from '../../services/global.service';
9 |
10 | @Component({
11 | selector: 'orders',
12 | templateUrl: './orders.component.html',
13 | styleUrls: ['./orders.component.scss']
14 | })
15 | export class OrdersComponent implements OnInit {
16 | user: Observable;
17 | userObject: any;
18 | userOrders: any;
19 | orderDates: any;
20 |
21 | constructor(
22 | public db: AngularFireDatabase,
23 | public globalService: GlobalService,
24 | public router: Router,
25 | public route: ActivatedRoute,
26 | public afAuth: AngularFireAuth,
27 | private title: Title,
28 | private meta: Meta
29 | ) {
30 | let userOrders;
31 | this.user = afAuth.authState;
32 | this.user.subscribe(currentUser => {
33 | if (currentUser && currentUser.uid) {
34 | this.userObject = currentUser;
35 | this.db.object('/users/' + currentUser.uid).valueChanges().subscribe((theuser:any) => {
36 | if (theuser && theuser.orders) {
37 | this.userOrders = Object.keys(theuser.orders);
38 | this.orderDates = Object.keys(theuser.orders).map(it => theuser.orders[it])
39 | }
40 | });
41 | } else {
42 | router.navigateByUrl('products');
43 | }
44 | });
45 | }
46 |
47 | ngOnInit() {
48 | this.title.setTitle('Orders');
49 | this.meta.updateTag({ content: 'View all of your past orders' }, "name='description'");
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/app/storefront-components/page/page.component.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/app/storefront-components/page/page.component.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alexabbott/firebase-cms/9b2e0ebd86485a2937b05dae0c2d5c365daf0562/src/app/storefront-components/page/page.component.scss
--------------------------------------------------------------------------------
/src/app/storefront-components/page/page.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { PageComponent } from './page.component';
4 |
5 | describe('PageComponent', () => {
6 | let component: PageComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async(() => {
10 | TestBed.configureTestingModule({
11 | declarations: [ PageComponent ]
12 | })
13 | .compileComponents();
14 | }));
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(PageComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should be created', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/src/app/storefront-components/page/page.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { ActivatedRoute, Params, Router } from '@angular/router';
3 | import { Title, Meta } from '@angular/platform-browser';
4 | import { AngularFireDatabase } from '@angular/fire/database';
5 |
6 | @Component({
7 | selector: 'page',
8 | templateUrl: './page.component.html',
9 | styleUrls: ['./page.component.scss']
10 | })
11 | export class PageComponent implements OnInit {
12 | pageContent: any;
13 | page: any;
14 |
15 | constructor(
16 | public db: AngularFireDatabase,
17 | public route: ActivatedRoute,
18 | private title: Title,
19 | private meta: Meta
20 | ) {}
21 |
22 | ngOnInit() {
23 | this.route.params.subscribe((params: Params) => {
24 | this.pageContent = this.db.list('/pages', ref => ref.orderByChild('url').equalTo(params.url));
25 | this.pageContent.valueChanges().subscribe(p => {
26 | if (p[0].published) {
27 | this.page = p[0];
28 | this.title.setTitle(this.page.title);
29 | this.meta.updateTag({ content: 'View ' + this.page.title }, "name='description'");
30 | } else {
31 | this.page = {
32 | title: 'Page Not Found',
33 | body: ''
34 | }
35 | }
36 | });
37 | });
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/app/storefront-components/pages/pages.component.html:
--------------------------------------------------------------------------------
1 |
2 | {{page.title}}
3 | {{page.body | truncate: 20 }}
4 |
5 |
--------------------------------------------------------------------------------
/src/app/storefront-components/pages/pages.component.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alexabbott/firebase-cms/9b2e0ebd86485a2937b05dae0c2d5c365daf0562/src/app/storefront-components/pages/pages.component.scss
--------------------------------------------------------------------------------
/src/app/storefront-components/pages/pages.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { PagesComponent } from './pages.component';
4 |
5 | describe('PagesComponent', () => {
6 | let component: PagesComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async(() => {
10 | TestBed.configureTestingModule({
11 | declarations: [ PagesComponent ]
12 | })
13 | .compileComponents();
14 | }));
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(PagesComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should be created', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/src/app/storefront-components/pages/pages.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { AngularFireDatabase } from '@angular/fire/database';
3 | import { Observable } from 'rxjs';
4 |
5 | @Component({
6 | selector: 'pages',
7 | templateUrl: './pages.component.html',
8 | styleUrls: ['./pages.component.scss']
9 | })
10 | export class PagesComponent implements OnInit {
11 | pages: Observable;
12 |
13 | constructor(db: AngularFireDatabase) {
14 | this.pages = db.list('/pages', ref => ref.orderByChild('title')).valueChanges();
15 | }
16 |
17 | ngOnInit() {
18 | }
19 |
20 | }
--------------------------------------------------------------------------------
/src/app/storefront-components/post/post.component.html:
--------------------------------------------------------------------------------
1 |
9 |
--------------------------------------------------------------------------------
/src/app/storefront-components/post/post.component.scss:
--------------------------------------------------------------------------------
1 | @import '../../../variables.scss';
2 |
3 | .post-header {
4 | p {
5 | color: $color-grey;
6 | font-size: 0.8em;
7 | }
8 |
9 | img {
10 | max-width: 150px;
11 | }
12 | }
--------------------------------------------------------------------------------
/src/app/storefront-components/post/post.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { PostComponent } from './post.component';
4 |
5 | describe('PostComponent', () => {
6 | let component: PostComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async(() => {
10 | TestBed.configureTestingModule({
11 | declarations: [ PostComponent ]
12 | })
13 | .compileComponents();
14 | }));
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(PostComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should be created', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/src/app/storefront-components/post/post.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { Title, Meta } from '@angular/platform-browser';
3 | import { ActivatedRoute, Params, Router } from '@angular/router';
4 | import { AngularFireDatabase } from '@angular/fire/database';
5 |
6 | @Component({
7 | selector: 'post',
8 | templateUrl: './post.component.html',
9 | styleUrls: ['./post.component.scss']
10 | })
11 | export class PostComponent implements OnInit {
12 | postContent: any;
13 | post: any;
14 |
15 | constructor(
16 | public db: AngularFireDatabase,
17 | public route: ActivatedRoute,
18 | private title: Title,
19 | private meta: Meta
20 | ) {}
21 |
22 | ngOnInit() {
23 | this.route.params.subscribe((params: Params) => {
24 | this.postContent = this.db.list('/posts', ref => ref.orderByChild('url').equalTo(params.url));
25 | this.postContent.valueChanges().subscribe(p => {
26 | if (p[0].published) {
27 | this.post = p[0];
28 | this.title.setTitle(this.post.title);
29 | this.meta.updateTag({ content: 'View ' + this.post.title }, "name='description'");
30 | } else {
31 | this.post = {
32 | title: 'Post Not Found',
33 | body: ''
34 | }
35 | }
36 | });
37 | });
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/app/storefront-components/posts/posts.component.html:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
11 |
{{post.date | date }}
12 |
13 |
14 |
15 |
16 |
17 |
18 |
No posts
19 |
--------------------------------------------------------------------------------
/src/app/storefront-components/posts/posts.component.scss:
--------------------------------------------------------------------------------
1 | @import '../../../variables.scss';
2 |
3 | .post {
4 | cursor: pointer;
5 | margin-bottom: 10px;
6 |
7 | @include tablet {
8 | margin-bottom: 5px;
9 | }
10 |
11 | img {
12 | display: inline-block;
13 | margin-right: 20px;
14 | max-width: 100%;
15 |
16 | @include tablet {
17 | max-width: 150px;
18 | }
19 | }
20 |
21 | h3 {
22 | @include tablet {
23 | margin-top: 0;
24 | }
25 | }
26 |
27 | .post-content {
28 | display: inline-block;
29 | vertical-align: top;
30 | width: 100%;
31 |
32 | @include tablet {
33 | width: calc(100% - 180px);
34 | }
35 | }
36 |
37 | .date {
38 | color: $color-grey;
39 | font-size: 0.8em;
40 | }
41 |
42 | button {
43 | float: right;
44 | }
45 | }
--------------------------------------------------------------------------------
/src/app/storefront-components/posts/posts.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { PostsComponent } from './posts.component';
4 |
5 | describe('PostsComponent', () => {
6 | let component: PostsComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async(() => {
10 | TestBed.configureTestingModule({
11 | declarations: [ PostsComponent ]
12 | })
13 | .compileComponents();
14 | }));
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(PostsComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should be created', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/src/app/storefront-components/posts/posts.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { Title, Meta } from '@angular/platform-browser';
3 | import { Router } from '@angular/router';
4 | import { AngularFireDatabase } from '@angular/fire/database';
5 | import { GlobalService } from '../../services/global.service';
6 | import { Observable } from 'rxjs';
7 |
8 | @Component({
9 | selector: 'posts',
10 | templateUrl: './posts.component.html',
11 | styleUrls: ['./posts.component.scss']
12 | })
13 | export class PostsComponent implements OnInit {
14 | posts: Observable;
15 | searchTerm: string;
16 |
17 | constructor(
18 | public db: AngularFireDatabase,
19 | public globalService: GlobalService,
20 | public router: Router,
21 | private title: Title,
22 | private meta: Meta
23 | ) {
24 | this.posts = db.list('/posts', ref => ref.orderByChild('published').equalTo(true).limitToLast(20)).valueChanges();
25 |
26 | this.globalService.searchTerm.subscribe((term) => {
27 | this.searchTerm = term;
28 | });
29 | }
30 |
31 | ngOnInit() {
32 | this.title.setTitle('Blog');
33 | this.meta.updateTag({ content: 'View recent blog posts' }, "name='description'");
34 |
35 | if (this.router.url.includes('blog')) {
36 | this.globalService.searchTerm.next('');
37 | }
38 | }
39 |
40 | getPostImage(post:any) {
41 | if (post.thumbnail) {
42 | return post.thumbnail;
43 | } else {
44 | return '../../assets/placeholder.jpg';
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/app/storefront-components/product-categories/product-categories.component.html:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/src/app/storefront-components/product-categories/product-categories.component.scss:
--------------------------------------------------------------------------------
1 | div {
2 | margin: 30px auto;
3 | text-align: center;
4 | width: 100%;
5 | }
--------------------------------------------------------------------------------
/src/app/storefront-components/product-categories/product-categories.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { ProductCategoriesComponent } from './product-categories.component';
4 |
5 | describe('ProductCategoriesComponent', () => {
6 | let component: ProductCategoriesComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async(() => {
10 | TestBed.configureTestingModule({
11 | declarations: [ ProductCategoriesComponent ]
12 | })
13 | .compileComponents();
14 | }));
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(ProductCategoriesComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should be created', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/src/app/storefront-components/product-categories/product-categories.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { Title, Meta } from '@angular/platform-browser';
3 | import { AngularFireDatabase } from '@angular/fire/database';
4 | import { Observable } from 'rxjs';
5 |
6 | @Component({
7 | selector: 'product-categories',
8 | templateUrl: './product-categories.component.html',
9 | styleUrls: ['./product-categories.component.scss']
10 | })
11 | export class ProductCategoriesComponent implements OnInit {
12 | categories: Observable;
13 |
14 | constructor(
15 | public db: AngularFireDatabase,
16 | private title: Title,
17 | private meta: Meta
18 | ) {
19 | this.categories = db.list('/categories', ref => ref.orderByChild('weight').limitToLast(999)).valueChanges();
20 | }
21 |
22 | ngOnInit() {
23 | this.title.setTitle('Products');
24 | this.meta.updateTag({ content: 'Browse products and product categories' }, "name='description'");
25 | }
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/src/app/storefront-components/product-category/product-category.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
14 |
15 |
16 | {{ product.price | currency:'USD':true }}
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/src/app/storefront-components/product-category/product-category.component.scss:
--------------------------------------------------------------------------------
1 | @import '../../../variables.scss';
2 |
3 | h3 {
4 | margin-left: .7%;
5 | text-align: center;
6 |
7 | @include tablet {
8 | text-align: left;
9 | }
10 | }
11 |
12 | .grid {
13 | display: flex;
14 | flex-flow: row;
15 | flex-wrap: wrap;
16 |
17 | .grid-tile {
18 | height: 300px;
19 | width: 100%;
20 |
21 | @include tablet {
22 | width: 50%;
23 | }
24 |
25 | @include desktop {
26 | width: 25%;
27 | }
28 | }
29 | }
30 |
31 | .product,
32 | .see-more {
33 | cursor: pointer;
34 | height: 235px;
35 | margin: 0 auto;
36 | width: auto;
37 |
38 | @include tablet {
39 | width: 80%;
40 | }
41 |
42 | .price {
43 | color: $color-grey;
44 | font-size: 0.8em;
45 | }
46 |
47 | img {
48 | display: block;
49 | height: 155px;
50 | margin: 0 auto;
51 | max-height: 155px;
52 | max-width: 100%;
53 | object-fit: cover;
54 | }
55 |
56 | .product-content {
57 | display: block;
58 | }
59 | }
60 |
61 | .grid .button-container {
62 | box-sizing: border-box;
63 | display: inline-block;
64 | height: 70px;
65 | margin: 0 auto;
66 | padding-top: 30px;
67 | text-align: center;
68 |
69 | @include tablet {
70 | height: 300px;
71 | padding-top: 130px;
72 | }
73 | }
74 |
75 | .categories {
76 | padding: 30px 30px 0 30px;
77 | }
78 |
79 | .categories .grid span {
80 | display: none;
81 | }
82 |
83 | .categories .grid span:nth-of-type(1),
84 | .categories .grid span:nth-of-type(2),
85 | .categories .grid span:nth-of-type(3) {
86 | display: block;
87 | }
88 |
89 | .categories .grid span:nth-of-type(1) + .button-container,
90 | .categories .grid span:nth-of-type(2) + .button-container {
91 | display: none;
92 | }
93 |
94 | .category .button-container {
95 | display: none;
96 | }
97 |
98 | .see-more:not([class*=mat-elevation-z]) {
99 | cursor: default;
100 | }
101 |
102 | .bottom-cta {
103 | margin-bottom: 30px;
104 | text-align: center;
105 | }
--------------------------------------------------------------------------------
/src/app/storefront-components/product-category/product-category.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { ProductCategoryComponent } from './product-category.component';
4 |
5 | describe('ProductCategoryComponent', () => {
6 | let component: ProductCategoryComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async(() => {
10 | TestBed.configureTestingModule({
11 | declarations: [ ProductCategoryComponent ]
12 | })
13 | .compileComponents();
14 | }));
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(ProductCategoryComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should be created', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/src/app/storefront-components/product-category/product-category.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, Input } from '@angular/core';
2 | import { ActivatedRoute, Params, Router } from '@angular/router';
3 | import { Title, Meta } from '@angular/platform-browser';
4 | import { AngularFireDatabase } from '@angular/fire/database';
5 | import { Observable } from 'rxjs';
6 |
7 | @Component({
8 | selector: 'product-category',
9 | templateUrl: './product-category.component.html',
10 | styleUrls: ['./product-category.component.scss']
11 | })
12 | export class ProductCategoryComponent implements OnInit {
13 | products: Observable;
14 | categories: Observable;
15 | category: Observable;
16 | categoryObject: any;
17 | categoryName: string;
18 | categoryProducts: any;
19 | @Input() categoryInput: any;
20 |
21 | constructor(
22 | public db: AngularFireDatabase,
23 | public route: ActivatedRoute,
24 | public router: Router,
25 | private title: Title,
26 | private meta: Meta
27 | ) {
28 | this.categories = db.list('/categories').valueChanges();
29 | this.products = db.list('/products', ref => ref.orderByChild('published').equalTo(true)).valueChanges();
30 | this.categoryObject = {};
31 | }
32 |
33 | ngOnInit() {
34 | if (this.categoryInput) {
35 | this.category = this.categoryInput;
36 | this.categoryObject.slug = this.categoryInput.slug;
37 | this.categoryObject.name = this.categoryInput.name;
38 | this.categoryObject.products = Object.keys(this.categoryInput.products);
39 | this.products.subscribe((p:any) => {
40 | this.categoryProducts = p.filter((item) => {
41 | return item.category === this.categoryInput.entityKey;
42 | });
43 | });
44 | } else {
45 | this.route.params.subscribe((params: Params) => {
46 | this.category = this.db.list('/categories', ref => ref.orderByChild('slug').equalTo(params.slug)).valueChanges();
47 |
48 | this.category.subscribe((cat:any) => {
49 | this.categoryObject.slug = cat[0].slug;
50 | this.categoryObject.name = cat[0].name;
51 | this.categoryObject.products = Object.keys(cat[0].products);
52 | this.products.subscribe((p:any) => {
53 | this.categoryProducts = p.filter((item:any) => {
54 | return item.category === cat[0].entityKey;
55 | });
56 | });
57 |
58 | this.title.setTitle(this.categoryObject.name);
59 | this.meta.addTag({ name: 'description', content: 'View all products in the ' + this.categoryObject.name + ' category' });
60 | });
61 | });
62 | }
63 | }
64 |
65 | getProductImage(product:any) {
66 | if (product && product.thumbnail) {
67 | return product.thumbnail;
68 | } else {
69 | return '../../assets/placeholder.jpg';
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/app/storefront-components/product/product.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
![{{product.title}}]()
4 |

5 |
6 |
7 |
{{product.title}}
8 |
9 |
{{product.price | currency:'USD':true}}
10 |
11 |
Qty
12 |
13 |
14 |
15 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/src/app/storefront-components/product/product.component.scss:
--------------------------------------------------------------------------------
1 | @import '../../../variables.scss';
2 |
3 | .container {
4 | display: flex;
5 | flex-direction: column;
6 |
7 | @include tablet {
8 | flex-direction: row;
9 | }
10 | }
11 |
12 | .featured-image {
13 | max-width: 100%;
14 | }
15 |
16 | .product-left,
17 | .product-right {
18 | box-sizing: border-box;
19 | display: inline-block;
20 | width: 100%;
21 |
22 | @include tablet {
23 | padding: 20px;
24 | }
25 | }
26 |
27 | .product-left {
28 | text-align: right;
29 | }
30 |
31 | .quantity-label,
32 | .quantity {
33 | display: inline-block;
34 | }
35 |
36 | .quantity-label {
37 | color: $color-grey;
38 | transform: translateY(-2px);
39 | }
40 |
41 | .quantity {
42 | width: 35px;
43 | }
--------------------------------------------------------------------------------
/src/app/storefront-components/product/product.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { ProductComponent } from './product.component';
4 |
5 | describe('ProductComponent', () => {
6 | let component: ProductComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async(() => {
10 | TestBed.configureTestingModule({
11 | declarations: [ ProductComponent ]
12 | })
13 | .compileComponents();
14 | }));
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(ProductComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should be created', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/src/app/storefront-components/products/products.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
{{product.price | currency:'USD':true }}
14 |
15 |
16 |
17 |
No products
18 |
19 |
--------------------------------------------------------------------------------
/src/app/storefront-components/products/products.component.scss:
--------------------------------------------------------------------------------
1 | @import '../../../variables.scss';
2 |
3 | .grid {
4 | display: flex;
5 | flex-flow: row;
6 | flex-wrap: wrap;
7 |
8 | .grid-tile {
9 | height: 300px;
10 | width: 100%;
11 |
12 | @include tablet {
13 | width: 50%;
14 | }
15 |
16 | @include desktop {
17 | width: 25%;
18 | }
19 | }
20 | }
21 |
22 | .product {
23 | cursor: pointer;
24 | height: 235px;
25 | margin: 0 auto;
26 | width: auto;
27 |
28 | @include tablet {
29 | width: 80%;
30 | }
31 |
32 | .price {
33 | color: $color-grey;
34 | font-size: 0.8em;
35 | }
36 |
37 | img {
38 | display: block;
39 | height: 155px;
40 | margin: 0 auto;
41 | max-height: 155px;
42 | max-width: 100%;
43 | object-fit: cover;
44 | }
45 |
46 | .product-content {
47 | display: block;
48 | }
49 | }
--------------------------------------------------------------------------------
/src/app/storefront-components/products/products.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { ProductsComponent } from './products.component';
4 |
5 | describe('ProductsComponent', () => {
6 | let component: ProductsComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async(() => {
10 | TestBed.configureTestingModule({
11 | declarations: [ ProductsComponent ]
12 | })
13 | .compileComponents();
14 | }));
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(ProductsComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should be created', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/src/app/storefront-components/products/products.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { Title, Meta } from '@angular/platform-browser';
3 | import { AngularFireDatabase } from '@angular/fire/database';
4 | import { Router } from '@angular/router';
5 | import { GlobalService } from '../../services/global.service';
6 | import { Observable } from 'rxjs';
7 |
8 | @Component({
9 | selector: 'products',
10 | templateUrl: './products.component.html',
11 | styleUrls: ['./products.component.scss']
12 | })
13 | export class ProductsComponent implements OnInit {
14 | products: Observable;
15 | searchTerm: string;
16 |
17 | constructor(
18 | public db: AngularFireDatabase,
19 | public globalService: GlobalService,
20 | public router: Router,
21 | private title: Title,
22 | private meta: Meta
23 | ) {
24 | this.products = db.list('/products', ref => ref.orderByChild('published').equalTo(true).limitToLast(20)).valueChanges();
25 |
26 | this.globalService.searchTerm.subscribe((term) => {
27 | this.searchTerm = term;
28 | });
29 | }
30 |
31 | ngOnInit() {
32 | this.title.setTitle('Products');
33 | this.meta.updateTag({ content: 'View all products' }, "name='description'");
34 |
35 | if (this.router.url.includes('product')) {
36 | this.globalService.searchTerm.next('');
37 | }
38 | }
39 |
40 | getProductImage(product:any) {
41 | if (product.thumbnail) {
42 | return product.thumbnail;
43 | } else {
44 | return '../../assets/placeholder.jpg';
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/app/storefront-components/search-results/search-results.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
Products
6 |
7 |
8 |
Blog Posts
9 |
10 |
--------------------------------------------------------------------------------
/src/app/storefront-components/search-results/search-results.component.scss:
--------------------------------------------------------------------------------
1 | .highlight {
2 | box-sizing: border-box;
3 | margin: 30px 30px 0 30px;
4 | width: calc(100% - 60px);
5 | }
6 |
7 | .search-big {
8 | display: block;
9 | margin: 30px 30px 0 30px;
10 | width: calc(100% - 60px);
11 | }
--------------------------------------------------------------------------------
/src/app/storefront-components/search-results/search-results.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { SearchResultsComponent } from './search-results.component';
4 |
5 | describe('SearchResultsComponent', () => {
6 | let component: SearchResultsComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async(() => {
10 | TestBed.configureTestingModule({
11 | declarations: [ SearchResultsComponent ]
12 | })
13 | .compileComponents();
14 | }));
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(SearchResultsComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should be created', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/src/app/storefront-components/search-results/search-results.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, ViewChild, ElementRef, ChangeDetectorRef } from '@angular/core';
2 | import { Title, Meta } from '@angular/platform-browser';
3 | import { GlobalService } from '../../services/global.service';
4 |
5 | @Component({
6 | selector: 'app-search-results',
7 | templateUrl: './search-results.component.html',
8 | styleUrls: ['./search-results.component.scss']
9 | })
10 | export class SearchResultsComponent implements OnInit {
11 | @ViewChild('searchit') private elementRef: ElementRef;
12 | searchTerm: string;
13 |
14 | constructor(
15 | public globalService: GlobalService,
16 | private cdRef: ChangeDetectorRef,
17 | private title: Title,
18 | private meta: Meta
19 | ) {
20 | this.globalService.searchTerm.next('');
21 | this.globalService.searchTerm.subscribe((term) => {
22 | this.searchTerm = term;
23 | });
24 | }
25 |
26 | ngOnInit() {
27 | this.title.setTitle('Search');
28 | this.meta.updateTag({ content: 'Search products and blog posts' }, "name='description'");
29 | }
30 |
31 | public ngAfterViewInit(): void {
32 | this.elementRef.nativeElement.focus();
33 | this.cdRef.detectChanges();
34 | }
35 |
36 | performSearch(event) {
37 | this.globalService.searchTerm.next(event);
38 | }
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/src/assets/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alexabbott/firebase-cms/9b2e0ebd86485a2937b05dae0c2d5c365daf0562/src/assets/.gitkeep
--------------------------------------------------------------------------------
/src/assets/google-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alexabbott/firebase-cms/9b2e0ebd86485a2937b05dae0c2d5c365daf0562/src/assets/google-icon.png
--------------------------------------------------------------------------------
/src/assets/icons/icon-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alexabbott/firebase-cms/9b2e0ebd86485a2937b05dae0c2d5c365daf0562/src/assets/icons/icon-144x144.png
--------------------------------------------------------------------------------
/src/assets/icons/icon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alexabbott/firebase-cms/9b2e0ebd86485a2937b05dae0c2d5c365daf0562/src/assets/icons/icon-16x16.png
--------------------------------------------------------------------------------
/src/assets/icons/icon-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alexabbott/firebase-cms/9b2e0ebd86485a2937b05dae0c2d5c365daf0562/src/assets/icons/icon-192x192.png
--------------------------------------------------------------------------------
/src/assets/icons/icon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alexabbott/firebase-cms/9b2e0ebd86485a2937b05dae0c2d5c365daf0562/src/assets/icons/icon-32x32.png
--------------------------------------------------------------------------------
/src/assets/icons/icon-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alexabbott/firebase-cms/9b2e0ebd86485a2937b05dae0c2d5c365daf0562/src/assets/icons/icon-512x512.png
--------------------------------------------------------------------------------
/src/assets/placeholder.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alexabbott/firebase-cms/9b2e0ebd86485a2937b05dae0c2d5c365daf0562/src/assets/placeholder.jpg
--------------------------------------------------------------------------------
/src/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alexabbott/firebase-cms/9b2e0ebd86485a2937b05dae0c2d5c365daf0562/src/favicon.ico
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Firebase Shop + Blog
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import { enableProdMode } from '@angular/core';
2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
3 |
4 | import { AppModule } from './app/app.module';
5 | import { environment } from './environments/environment';
6 |
7 | if (environment.production) {
8 | enableProdMode();
9 | }
10 |
11 | platformBrowserDynamic().bootstrapModule(AppModule);
12 |
--------------------------------------------------------------------------------
/src/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "dir": "ltr",
3 | "lang": "en",
4 | "name": "FireShop",
5 | "scope": "/",
6 | "display": "standalone",
7 | "start_url": "./?utm_source=web_app_manifest",
8 | "short_name": "FireShop",
9 | "theme_color": "#2E2E2E",
10 | "description": "",
11 | "orientation": "any",
12 | "background_color": "#2E2E2E",
13 | "related_applications": [],
14 | "prefer_related_applications": false,
15 | "icons": [
16 | {
17 | "src": "/assets/icons/icon-512x512.png",
18 | "sizes": "512x512"
19 | },
20 | {
21 | "src": "/assets/icons/icon-192x192.png",
22 | "sizes": "192x192"
23 | },
24 | {
25 | "src": "/assets/icons/icon-144x144.png",
26 | "sizes": "144x144"
27 | },
28 | {
29 | "src": "/assets/icons/icon-32x32.png",
30 | "sizes": "32x32"
31 | },
32 | {
33 | "src": "/assets/icons/icon-16x16.png",
34 | "sizes": "16x16"
35 | }
36 | ]
37 | }
38 |
--------------------------------------------------------------------------------
/src/test.ts:
--------------------------------------------------------------------------------
1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files
2 |
3 | import 'zone.js/dist/long-stack-trace-zone';
4 | import 'zone.js/dist/proxy.js';
5 | import 'zone.js/dist/sync-test';
6 | import 'zone.js/dist/jasmine-patch';
7 | import 'zone.js/dist/async-test';
8 | import 'zone.js/dist/fake-async-test';
9 | import { getTestBed } from '@angular/core/testing';
10 | import {
11 | BrowserDynamicTestingModule,
12 | platformBrowserDynamicTesting
13 | } from '@angular/platform-browser-dynamic/testing';
14 |
15 | // Unfortunately there's no typing for the `__karma__` variable. Just declare it as any.
16 | declare const __karma__: any;
17 | declare const require: any;
18 |
19 | // Prevent Karma from running prematurely.
20 | __karma__.loaded = function () {};
21 |
22 | // First, initialize the Angular testing environment.
23 | getTestBed().initTestEnvironment(
24 | BrowserDynamicTestingModule,
25 | platformBrowserDynamicTesting()
26 | );
27 | // Then we find all the tests.
28 | const context = require.context('./', true, /\.spec\.ts$/);
29 | // And load the modules.
30 | context.keys().map(context);
31 | // Finally, start Karma to run the tests.
32 | __karma__.start();
33 |
--------------------------------------------------------------------------------
/src/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../out-tsc/app",
5 | "module": "es2015",
6 | "baseUrl": "",
7 | "types": []
8 | },
9 | "exclude": [
10 | "test.ts",
11 | "**/*.spec.ts"
12 | ]
13 | }
14 |
--------------------------------------------------------------------------------
/src/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../out-tsc/spec",
5 | "module": "commonjs",
6 | "target": "es5",
7 | "baseUrl": "",
8 | "types": [
9 | "jasmine",
10 | "node"
11 | ]
12 | },
13 | "files": [
14 | "test.ts"
15 | ],
16 | "include": [
17 | "**/*.spec.ts",
18 | "**/*.d.ts"
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/src/typings.d.ts:
--------------------------------------------------------------------------------
1 | /* SystemJS module definition */
2 | declare var module: NodeModule;
3 | interface NodeModule {
4 | id: string;
5 | }
6 |
--------------------------------------------------------------------------------
/src/variables.scss:
--------------------------------------------------------------------------------
1 | // colors
2 | $color-main: #2E2E2E;
3 | $color-white: #FFF;
4 | $color-grey: #777;
5 | $color-grey-dark: #333;
6 | $color-grey-light: #EEE;
7 | $color-black: rgba(0,0,0,.87);
8 | $color-red: #FF0000;
9 | $color-green: #009900;
10 |
11 | // screen size
12 | $screen-sm: 750px;
13 | $screen-md: 960px;
14 |
15 | // transitions
16 | %transition {
17 | transition: .3s ease-out;
18 | }
19 |
20 | // mixins
21 | @mixin mobile {
22 | @media screen and (max-width: #{$screen-sm}) {
23 | @content;
24 | }
25 | }
26 |
27 | @mixin tablet {
28 | @media screen and (min-width: #{$screen-sm}) {
29 | @content;
30 | }
31 | }
32 |
33 | @mixin desktop {
34 | @media screen and (min-width: #{$screen-md}) {
35 | @content;
36 | }
37 | }
--------------------------------------------------------------------------------
/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "extends": "./tsconfig.json",
4 | "compilerOptions": {
5 | "outDir": "./out-tsc/app",
6 | "types": []
7 | },
8 | "files": [
9 | "src/main.ts",
10 | "src/polyfills.ts"
11 | ],
12 | "include": [
13 | "src/**/*.d.ts"
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "compileOnSave": false,
4 | "compilerOptions": {
5 | "baseUrl": "./",
6 | "outDir": "./dist/out-tsc",
7 | "sourceMap": true,
8 | "declaration": false,
9 | "downlevelIteration": true,
10 | "experimentalDecorators": true,
11 | "moduleResolution": "node",
12 | "importHelpers": true,
13 | "target": "es2015",
14 | "module": "es2020",
15 | "lib": [
16 | "es2018",
17 | "dom"
18 | ]
19 | },
20 | "angularCompilerOptions": {
21 | "enableI18nLegacyMessageIdFormat": false
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "extends": "./tsconfig.json",
4 | "compilerOptions": {
5 | "outDir": "./out-tsc/spec",
6 | "types": [
7 | "jasmine"
8 | ]
9 | },
10 | "files": [
11 | "src/test.ts",
12 | "src/polyfills.ts"
13 | ],
14 | "include": [
15 | "src/**/*.spec.ts",
16 | "src/**/*.d.ts"
17 | ]
18 | }
19 |
--------------------------------------------------------------------------------