Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add features: #32

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .browserslistrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
> 1%
last 2 versions
not dead
5 changes: 5 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[*.{js,jsx,ts,tsx,vue}]
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
insert_final_newline = true
17 changes: 17 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
module.exports = {
root: true,
env: {
node: true
},
extends: [
'plugin:vue/essential',
'@vue/standard'
],
parserOptions: {
parser: 'babel-eslint'
},
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off'
}
}
10 changes: 10 additions & 0 deletions .firebase/hosting.ZGlzdA.cache
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
index.html,1607785606000,776e872fd8b97183a18c4c65ff57dfcd44b5fe7e90e33f53fad955e46fbc9286
css/app.3c7397b8.css,1607785606000,455f9579e810df378d526285aa8c894f9c0fa29c3237ebc8758d6a94639c81a8
favicon.ico,1607785606000,e0535b2041a7a1721cdec785c903980d41fdf0d810a4ea9726b6ffd1371bbc28
js/about.fa2c2416.js,1607785606000,2004444f8988c52a3457968827ba75380f35e0d7f530c2885dfb3e22dc3474b0
js/app.8b6f67c3.js,1607785606024,d8795ce02f0b51b7d73958c5d9f14ab8c6538126d12375f496fd0d68300d4793
js/about.fa2c2416.js.map,1607785606024,cc0f8643b1543e35301b749e246bab81215cd67077fe51e6b436154fcdfc126b
js/app.8b6f67c3.js.map,1607785606024,9290c0fa66738e7e8c49f7f14600b6c2e62dcf046ef6de7b0b6fe9e5423e74f7
css/chunk-vendors.2d065fb2.css,1607785606024,e170c187a2feaff14a2d395cccb83c205bd86349b84649e0c0a72e62318a39fd
js/chunk-vendors.385325db.js,1607785606024,f6b896d27590af3267ffb0a0bb8199596f16294a1ac4db5297bc56866a1961c1
js/chunk-vendors.385325db.js.map,1607785606024,ba4897c8a3ea4104c83d2c8d49b3e6b7d2833c662918ba3e168a3bf981742375
5 changes: 5 additions & 0 deletions .firebaserc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"projects": {
"default": "ecommers-cmsx"
}
}
23 changes: 23 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
.DS_Store
node_modules
/dist


# local env files
.env.local
.env.*.local

# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*

# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
27 changes: 26 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,26 @@
# ecommerce_client_cms
# ecommerce_client_cms

https://ecommers-cmsx.web.app/

## Project setup
```
npm install
```

### Compiles and hot-reloads for development
```
npm run serve
```

### Compiles and minifies for production
```
npm run build
```

### Lints and fixes files
```
npm run lint
```

### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).
5 changes: 5 additions & 0 deletions babel.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
}
16 changes: 16 additions & 0 deletions firebase.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"hosting": {
"public": "dist",
"ignore": [
"firebase.json",
"**/.*",
"**/node_modules/**"
],
"rewrites": [
{
"source": "**",
"destination": "/index.html"
}
]
}
}
12,564 changes: 12,564 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

36 changes: 36 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"name": "ecommerce_client_cms",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
"dependencies": {
"axios": "^0.21.0",
"bootstrap": "^4.5.3",
"bootstrap-vue": "^2.20.1",
"core-js": "^3.6.5",
"vue": "^2.6.12",
"vue-google-login": "^2.0.5",
"vue-router": "^3.2.0",
"vuex": "^3.4.0"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~4.5.0",
"@vue/cli-plugin-eslint": "~4.5.0",
"@vue/cli-plugin-router": "~4.5.0",
"@vue/cli-plugin-vuex": "^4.5.9",
"@vue/cli-service": "~4.5.0",
"@vue/eslint-config-standard": "^5.1.2",
"babel-eslint": "^10.1.0",
"eslint": "^6.7.2",
"eslint-plugin-import": "^2.20.2",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^4.2.1",
"eslint-plugin-standard": "^4.0.0",
"eslint-plugin-vue": "^6.2.2",
"vue-template-compiler": "^2.6.11"
}
}
Binary file added public/favicon.ico
Binary file not shown.
19 changes: 19 additions & 0 deletions public/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<meta name="google-signin-client_id" content="1048832564850-fpn38itn9av9bci2rfeoedhnih2sqnsi.apps.googleusercontent.com">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
<script src="https://apis.google.com/js/platform.js" async defer></script>
</body>
</html>
34 changes: 34 additions & 0 deletions src/App.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<template>
<div id="app">
<router-view/>
</div>
</template>

<style>
html,
body {
height: 100%;
}
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
height: 100%;
}
#nav {
padding: 30px;
}
#nav a {
font-weight: bold;
color: #2c3e50;
}
#nav a.router-link-exact-active {
color: #42b983;
}
</style>
Binary file added src/assets/logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
145 changes: 145 additions & 0 deletions src/components/ModalUpdate.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
<template>
<div>
<a class="btn btn-primary" v-b-modal.modal-prevent-closing @click="populate(productsById)">Update</a>

<div class="mt-3">
</div>

<b-modal
id="modal-prevent-closing"
ref="modal"
title="Update Your Product"
@hidden="resetModal"
@ok="handleOk"
>
<form ref="form" @submit.stop.prevent="handleSubmit">
<b-form-group
:state="nameState"
label="Name"
label-for="name-input"
invalid-feedback="Name is required"
>
<b-form-input
id="name-input"
v-model="name"
:state="nameState"
required
></b-form-input>
</b-form-group>

<b-form-group
:state="imageUrlState"
label="imageUrl"
label-for="imageUrl-input"
invalid-feedback="Image Url is required"
>
<b-form-input
id="imageUrl-input"
v-model="imageUrl"
:state="imageUrlState"
required
></b-form-input>
</b-form-group>

<b-form-group
:state="priceState"
label="Price"
label-for="price-input"
invalid-feedback="Price is required"
>
<b-form-input
id="price-input"
v-model="price"
:state="priceState"
required
></b-form-input>
</b-form-group>

<b-form-group
:state="stockState"
label="Stock"
label-for="stock-input"
invalid-feedback="Stock is required"
>
<b-form-input
id="stock-input"
v-model="stock"
:state="stockState"
required
></b-form-input>
</b-form-group>
</form>
</b-modal>
</div>
</template>

<script>
export default {
props: ['productsById'],
data () {
return {
name: '',
nameState: null,
imageUrl: '',
imageUrlState: null,
price: '',
priceState: null,
stock: '',
stockState: null
}
},
methods: {
checkFormValidity () {
const valid = this.$refs.form.checkValidity()
this.nameState = valid
this.imageUrlState = valid
this.priceState = valid
this.stockState = valid
return valid
},
resetModal () {
this.name = ''
this.nameState = null
this.imageUrl = ''
this.imageUrlState = null
this.price = ''
this.priceState = null
this.stock = ''
this.stockState = null
},
handleOk (bvModalEvt) {
// Prevent modal from closing
bvModalEvt.preventDefault()
// Trigger submit handler
this.handleSubmit()
},
handleSubmit () {
// Exit when the form isn't valid
if (!this.checkFormValidity()) {
return
}
this.$store.dispatch('updateProduct', {
id: this.$route.params.id,
name: this.name,
imageUrl: this.imageUrl,
price: this.price,
stock: this.stock
})
// Hide the modal manually
this.$nextTick(() => {
this.$bvModal.hide('modal-prevent-closing')
})
},
populate (obj) {
// console.log(obj)
this.name = obj.name
this.imageUrl = obj.imageUrl
this.price = obj.price
this.stock = obj.stock
}
}
}
</script>

<style>
</style>
159 changes: 159 additions & 0 deletions src/components/ModalUpdateBanner.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
<template>
<div>
<a class="btn btn-primary" v-b-modal="bannerId+''" @click="populate(bannerId)">Update</a>

<div class="mt-3">
</div>

<b-modal
:id="bannerId+''"
ref="modal"
title="Update Your Banner"
@hidden="resetModal"
@ok="handleOk"
>
<form ref="form" @submit.stop.prevent="handleSubmit">
<b-form-group
:state="titleState"
label="Title"
label-for="title-input"
invalid-feedback="Title is required"
>
<b-form-input
id="title-input"
v-model="title"
:state="titleState"
required
></b-form-input>
</b-form-group>

<b-form-group
:state="imageUrlState"
label="imageUrl"
label-for="imageUrl-input"
invalid-feedback="Image Url is required"
>
<b-form-input
id="imageUrl-input"
v-model="imageUrl"
:state="imageUrlState"
required
></b-form-input>
</b-form-group>

<b-form-group
label="Status"
label-for="status-input"
>
<div>
<b-form-select
v-model="selected"
:options="options"
class="mb-3"
value-field="item"
text-field="name"
disabled-field="notEnabled"
></b-form-select>
<!-- <div class="mt-3">Selected: <strong>{{ selected }}</strong></div> -->
</div>
</b-form-group>

</form>
</b-modal>
</div>
</template>

<script>
export default {
props: ['bannerId'],
data () {
return {
title: '',
titleState: null,
imageUrl: '',
imageUrlState: null,
selected: 'Active',
options: [
{ item: 'Active', name: 'Active' },
{ item: 'Inactive', name: 'Inactive' }
],
updateId: null
}
},
methods: {
fetchBannerById (id) {
this.$store.dispatch('fetchBannerById', id)
},
checkFormValidity () {
const valid = this.$refs.form.checkValidity()
this.nameState = valid
this.imageUrlState = valid
this.priceState = valid
this.stockState = valid
return valid
},
resetModal () {
this.name = ''
this.nameState = null
this.imageUrl = ''
this.imageUrlState = null
this.price = ''
this.priceState = null
this.stock = ''
this.stockState = null
},
handleOk (bvModalEvt) {
// Prevent modal from closing
bvModalEvt.preventDefault()
// Trigger submit handler
this.handleSubmit()
},
handleSubmit () {
// Exit when the form isn't valid
if (!this.checkFormValidity()) {
return
}
this.$store.dispatch('updateBanner', {
id: this.updateId,
title: this.title,
imageUrl: this.imageUrl,
status: this.selected
})
.then(_ => {
// router.push({ name: 'Banner' })
this.$store.dispatch('fetchBanners')
// this.fetchBannerById()
})
.catch(err => {
console.log(err)
})
.finally(_ => {
this.$nextTick(() => {
this.$bvModal.hide(this.bannerId + '')
})
})
// Hide the modal manually
},
bannerById () {
return this.$store.state.bannerById
},
populate (id) {
this.$store.dispatch('fetchBannerById', id)
.then(data => {
// console.log(data.data)
const obj = data.data
this.title = obj.title
this.imageUrl = obj.imageUrl
this.selected = obj.status
this.updateId = obj.id
})
.catch(err => {
console.log(err)
})
}
}
}
</script>

<style>
</style>
72 changes: 72 additions & 0 deletions src/components/Navbar.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<template>
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<a class="navbar-brand" href="#">E-Commerce</a>
<button
class="navbar-toggler"
type="button"
data-toggle="collapse"
data-target="#navbarNav"
aria-controls="navbarNav"
aria-expanded="false"
aria-label="Toggle navigation"
>
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav">
<li>
<router-link to="/" class="nav-link">Home</router-link>
</li>
<li>
<router-link to="/products/add" class="nav-link">Add Product</router-link>
</li>
<li>
<router-link to="/banner" class="nav-link">Banner</router-link>
</li>
<li>
<router-link to="/banner/add" class="nav-link">Add Banner</router-link>
</li>
</ul>
<ul class="navbar-nav ml-auto">
<li>
<a href="#" @click="logout" style="text-decoration: none"
><GoogleLogin
:params="params"
:logoutButton="true"
class="nav-link btn btn-danger p-1 w"
style="color: black"
>Logout</GoogleLogin
><span class="sr-only">(current)</span></a>
<!-- <button class="btn btn-danger" @click="logout">Logout</button> -->
</li>
</ul>
</div>
</nav>
</template>

<script>
import GoogleLogin from 'vue-google-login'
export default {
name: 'Navbar',
components: {
GoogleLogin
},
data () {
return {
params: {
client_id:
'1048832564850-fpn38itn9av9bci2rfeoedhnih2sqnsi.apps.googleusercontent.com'
}
}
},
methods: {
logout () {
this.$store.dispatch('logout')
}
}
}
</script>

<style>
</style>
59 changes: 59 additions & 0 deletions src/components/ProductCard.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<template>
<div class="col-md-6">
<div class="card shadow">
<img
class="card-img-top"
:src="product.imageUrl"
alt="Card image cap"
style="height: 250px"
/>
<div class="card-body">
<h5 class="card-title">{{ product.name }}</h5>
<ul class="list-group">
<li
class="list-group-item d-flex justify-content-between align-items-center"
>
Price
<span class="badge badge-primary badge-pill"
>Rp {{ product.price.toLocaleString("id") }}</span
>
</li>
<li
class="list-group-item d-flex justify-content-between align-items-center"
>
Stocks
<span class="badge badge-primary badge-pill">{{
product.stock
}}</span>
</li>
</ul>
<a class="btn btn-primary mt-2" @click.prevent="detail(product.id)">Edit</a>
<a class="btn btn-danger mt-2 ml-2" @click.prevent="destroyProd(product.id)">Delete</a>
</div>
</div>
</div>
</template>

<script>
export default {
name: 'ProductCard',
props: ['product'],
methods: {
detail (id) {
this.$router.push(`products/${id}`)
},
destroyProd (id) {
this.$store.dispatch('destroyProd', id)
.then(_ => {
this.$store.dispatch('fetchProducts')
})
.catch(err => {
console.log(err)
})
}
}
}
</script>

<style>
</style>
7 changes: 7 additions & 0 deletions src/config/axiosInstance.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import axios from 'axios'

const instance = axios.create({
baseURL: 'https://ecommerce-cmsxs.herokuapp.com'
})

export default instance
20 changes: 20 additions & 0 deletions src/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import { BootstrapVue, IconsPlugin } from 'bootstrap-vue'
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'
import store from './store'

// Install BootstrapVue
Vue.use(BootstrapVue)
// Optionally install the BootstrapVue icon components plugin
Vue.use(IconsPlugin)

Vue.config.productionTip = false

new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
70 changes: 70 additions & 0 deletions src/router/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'
import Login from '../views/Login.vue'
import MainPage from '../views/MainPage.vue'
import PageNotFound from '../views/PageNotFound.vue'

Vue.use(VueRouter)

const routes = [
{
path: '/login',
name: 'Login',
component: Login
},
{
path: '/',
name: 'MainPage',
component: MainPage,
children: [
{
path: '',
name: 'Home',
component: Home
},
{
path: 'products/add',
name: 'AddProduct',
component: () => import(/* webpackChunkName: "about" */ '../views/AddProduct.vue')
},
{
path: 'banner',
name: 'Banner',
component: () => import(/* webpackChunkName: "about" */ '../views/Banner.vue')
},
{
path: 'banner/add',
name: 'AddBanner',
component: () => import(/* webpackChunkName: "about" */ '../views/AddBanner.vue')
},
{
path: 'products/:id',
name: 'Detail',
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import(/* webpackChunkName: "about" */ '../views/Detail.vue')
},
{
path: '*',
component: PageNotFound
}
]
}
]

const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})

router.beforeEach((to, from, next) => {
const isAuthenticated = localStorage.getItem('access_token')
if (to.name !== 'Login' && !isAuthenticated) next({ name: 'Login' })
else if (to.name === 'Login' && isAuthenticated) next({ name: 'MainPage' })
else next()
})

export default router
210 changes: 210 additions & 0 deletions src/store/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
import Vue from 'vue'
import Vuex from 'vuex'
import axios from '../config/axiosInstance'
import router from '../router'

Vue.use(Vuex)

export default new Vuex.Store({
state: {
products: [],
productsById: {},
banners: [],
bannerById: {}
},
mutations: {
setProducts (state, data) {
state.products = data
},
setProductsById (state, data) {
state.productsById = data
},
setBanners (state, data) {
state.banners = data
},
setBannerById (state, data) {
state.bannerById = data
}
},
actions: {
login (context, payload) {
axios({
method: 'POST',
url: '/login',
data: payload
})
.then(response => {
localStorage.setItem('access_token', response.data.access_token)
router.push({ name: 'Home' })
this.fetchProducts()
})
.catch(err => {
console.log(err)
})
},
logout () {
localStorage.clear()
router.push('/login')
},
fetchProducts (context) {
axios({
method: 'GET',
url: '/products',
headers: {
access_token: localStorage.getItem('access_token')
}
})
.then(data => {
context.commit('setProducts', data)
})
.catch(err => {
console.log(err)
})
},
fetchProductsById (context, id) {
axios({
method: 'GET',
url: '/products/' + id,
headers: {
access_token: localStorage.getItem('access_token')
}
})
.then(data => {
context.commit('setProductsById', data)
})
.catch(err => {
console.log(err)
})
},
updateProduct (context, obj) {
axios({
method: 'PUT',
url: '/products/' + obj.id,
headers: {
access_token: localStorage.getItem('access_token')
},
data: {
name: obj.name,
imageUrl: obj.imageUrl,
price: obj.price,
stock: obj.stock
}
})
.then(_ => {
router.push({ name: 'Home' })
this.fetchProductsById()
})
.catch(err => {
console.log(err)
})
},
destroyProd (context, id) {
return axios({
method: 'DELETE',
url: '/products/' + id,
headers: {
access_token: localStorage.getItem('access_token')
}
})
},
createProduct (context, obj) {
axios({
method: 'POST',
url: '/products',
headers: {
access_token: localStorage.getItem('access_token')
},
data: {
name: obj.name,
imageUrl: obj.imageUrl,
price: +obj.price,
stock: +obj.stock
}
})
.then(_ => {
router.push({ name: 'Home' })
})
.catch(err => {
console.log(err)
})
},
createBanner (context, obj) {
axios({
method: 'POST',
url: '/banners',
headers: {
access_token: localStorage.getItem('access_token')
},
data: {
title: obj.title,
imageUrl: obj.imageUrl,
status: obj.status
}
})
.then(_ => {
router.push({ name: 'Banner' })
})
.catch(err => {
console.log(err)
})
},
fetchBanners (context) {
axios({
method: 'GET',
url: '/banners',
headers: {
access_token: localStorage.getItem('access_token')
}
})
.then(data => {
context.commit('setBanners', data)
})
.catch(err => {
console.log(err)
})
},
destroyBanner (context, id) {
return axios({
method: 'DELETE',
url: '/banners/' + id,
headers: {
access_token: localStorage.getItem('access_token')
}
})
},
updateBanner (context, obj) {
return axios({
method: 'PUT',
url: '/banners/' + obj.id,
headers: {
access_token: localStorage.getItem('access_token')
},
data: {
title: obj.title,
imageUrl: obj.imageUrl,
status: obj.status
}
})
},
fetchBannerById (context, id) {
return axios({
method: 'GET',
url: '/banners/' + Number(id),
headers: {
access_token: localStorage.getItem('access_token')
}
})
},
loginGoogle (context, googleToken) {
return axios({
method: 'POST',
url: 'http://localhost:3000/googleLogin',
data: {
googleToken
}
})
}
},
modules: {
}
})
104 changes: 104 additions & 0 deletions src/views/AddBanner.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
<template>
<div class="container">
<h1 class="text-center mb-4">New Banner</h1>
<div>
<b-form @submit="onSubmit" @reset="onReset" v-if="show">
<b-form-group
id="input-group-1"
label="Title"
label-for="input-1"
>
<b-form-input
id="input-1"
v-model="form.title"
type="text"
required
placeholder="Enter banner title"
></b-form-input>
</b-form-group>

<b-form-group id="input-group-3" label="Status" label-for="input-3">
<!-- <b-form-input
type="number"
id="input-3"
v-model="form.status"
required
placeholder="Input status"
></b-form-input> -->
<div>
<b-form-select
v-model="selected"
:options="options"
class="mb-3"
value-field="item"
text-field="name"
disabled-field="notEnabled"
></b-form-select>
<!-- <div class="mt-3">Selected: <strong>{{ selected }}</strong></div> -->
</div>
</b-form-group>

<b-form-group id="input-group-2" label="Image URL" label-for="input-2">
<b-form-input
id="input-2"
v-model="form.imageUrl"
required
placeholder="Enter image url"
></b-form-input>
</b-form-group>

<b-button type="submit" variant="primary">Submit</b-button>
<b-button type="reset" variant="danger" class="ml-2">Reset</b-button>
</b-form>
<!-- <b-card class="mt-3" header="Form Data Result">
<pre class="m-0">{{ form }}</pre>
</b-card> -->
</div>
</div>
</template>

<script>
export default {
name: 'AddProduct',
data () {
return {
form: {
title: '',
imageUrl: ''
},
show: true,
selected: 'Active',
options: [
{ item: 'Active', name: 'Active' },
{ item: 'Inactive', name: 'Inactive' }
]
}
},
methods: {
onSubmit (evt) {
evt.preventDefault()
console.log(this.form.title, this.form.imageUrl, this.selected)
this.$store.dispatch('createBanner', {
title: this.form.title,
imageUrl: this.form.imageUrl,
status: this.selected
})
},
onReset (evt) {
evt.preventDefault()
// Reset our form values
this.form.title = ''
this.form.imageUrl = ''
this.form.status = ''
// Trick to reset/clear native browser form validation state
this.show = false
this.$nextTick(() => {
this.show = true
})
}
}
}
</script>

<style>
</style>
102 changes: 102 additions & 0 deletions src/views/AddProduct.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
<template>
<div class="container">
<h1 class="text-center mb-4">New Product</h1>
<div>
<b-form @submit="onSubmit" @reset="onReset" v-if="show">
<b-form-group
id="input-group-1"
label="Name"
label-for="input-1"
>
<b-form-input
id="input-1"
v-model="form.name"
type="text"
required
placeholder="Enter product name"
></b-form-input>
</b-form-group>

<b-form-group id="input-group-2" label="Image URL" label-for="input-2">
<b-form-input
id="input-2"
v-model="form.imageUrl"
required
placeholder="Enter image url"
></b-form-input>
</b-form-group>

<b-form-group id="input-group-3" label="Price" label-for="input-3">
<b-form-input
type="number"
id="input-3"
v-model="form.price"
required
placeholder="Input price"
></b-form-input>
</b-form-group>
<!-- <p>{{ form.price }}</p> -->
<b-form-group id="input-group-4" label="Stocks" label-for="input-4">
<b-form-input
id="input-4"
type="number"
v-model="form.stock"
required
placeholder="Input stocks"
></b-form-input>
</b-form-group>

<b-button type="submit" variant="primary">Submit</b-button>
<b-button type="reset" variant="danger" class="ml-2">Reset</b-button>
</b-form>
<!-- <b-card class="mt-3" header="Form Data Result">
<pre class="m-0">{{ form }}</pre>
</b-card> -->
</div>
</div>
</template>

<script>
export default {
name: 'AddProduct',
data () {
return {
form: {
name: '',
imageUrl: '',
price: null,
stock: null
},
show: true
}
},
methods: {
onSubmit (evt) {
evt.preventDefault()
console.log(this.form.name, this.form.imageUrl, this.form.price, this.form.stock)
this.$store.dispatch('createProduct', {
name: this.form.name,
imageUrl: this.form.imageUrl,
price: this.form.price,
stock: this.form.stock
})
},
onReset (evt) {
evt.preventDefault()
// Reset our form values
this.form.name = ''
this.form.imageUrl = ''
this.form.price = null
this.form.stock = null
// Trick to reset/clear native browser form validation state
this.show = false
this.$nextTick(() => {
this.show = true
})
}
}
}
</script>

<style>
</style>
65 changes: 65 additions & 0 deletions src/views/Banner.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<template>
<div class="container">
<h1 v-if="bannersData.data.length === 0">Oooppps.. belum ada banner</h1>
<table v-else class="table table-hover">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Banner</th>
<th scope="col">Title</th>
<th scope="col">Status</th>
<th scope="col">Action</th>
</tr>
</thead>
<tbody>
<tr v-for="(banner, i) in bannersData.data" :key="i">
<th scope="row">{{ i + 1 }}</th>
<td><img style="height: 100px; width: 100%;" :src="banner.imageUrl" alt="image"></td>
<td>{{ banner.title }}</td>
<td>{{ banner.status }}</td>
<td>
<!-- <button class="btn btn-primary">Edit</button> -->
<ModalUpdateBanner :bannerId="banner.id"></ModalUpdateBanner>
<button class="btn btn-danger" @click="destroyBanner(banner.id)">Delete</button></td>
</tr>
</tbody>
</table>
</div>
</template>

<script>
import ModalUpdateBanner from '../components/ModalUpdateBanner'
export default {
name: 'Banners',
components: {
ModalUpdateBanner
},
methods: {
fetchBanners () {
this.$store.dispatch('fetchBanners')
},
destroyBanner (id) {
this.$store.dispatch('destroyBanner', id)
.then(_ => {
this.$store.dispatch('fetchBanners')
})
.catch(err => {
console.log(err)
})
}
},
created () {
this.fetchBanners()
},
computed: {
bannersData () {
return this.$store.state.banners
}
}
}
</script>

<style>
</style>
65 changes: 65 additions & 0 deletions src/views/Detail.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<template>
<div class="detail container w-25">
<!-- <h1>This is an detail page of {{ productsById.data }}</h1> -->
<div class="card shadow">
<img
class="card-img-top"
style="height: 250px"
:src="productsById.data.imageUrl"
alt="Card image cap"
/>
<div class="card-body">
<h5 class="card-title">{{ productsById.data.name }}</h5>
<ul class="list-group">
<li
class="list-group-item d-flex justify-content-between align-items-center"
>
Price
<span class="badge badge-primary badge-pill"
>Rp {{ productsById.data.price.toLocaleString("id") }}</span
>
</li>
<li
class="list-group-item d-flex justify-content-between align-items-center"
>
Stocks
<span class="badge badge-primary badge-pill">{{
productsById.data.stock
}}</span>
</li>
</ul>
<!-- <a class="btn btn-primary mt-2" >Update</a> -->
<ModalUpdate :productsById="productsById.data" class="mt-4"></ModalUpdate>
</div>
</div>
</div>
</template>

<script>
import ModalUpdate from '../components/ModalUpdate'
export default {
name: 'Detail',
components: {
ModalUpdate
},
methods: {
fetchProductsById () {
const id = this.$route.params.id
this.$store.dispatch('fetchProductsById', id)
}
},
created () {
console.log(this.$route.params.id)
this.fetchProductsById()
},
computed: {
productsById () {
return this.$store.state.productsById
}
}
}
</script>

<style>
</style>
43 changes: 43 additions & 0 deletions src/views/Home.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<template>
<div class="home">
<!-- {{ productsData.data.length }} -->
<div class="container w-50">
<div class="row">
<ProductCard
class="mt-2"
v-for="product in productsData.data"
:key="product.id"
:product="product">
</ProductCard>
</div>
</div>
</div>
</template>

<script>
import ProductCard from '../components/ProductCard.vue'
export default {
name: 'Home',
components: {
ProductCard
},
data () {
return {
products: []
}
},
methods: {
fetchProducts () {
this.$store.dispatch('fetchProducts')
}
},
created () {
this.fetchProducts()
},
computed: {
productsData () {
return this.$store.state.products
}
}
}
</script>
105 changes: 105 additions & 0 deletions src/views/Login.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
<template>
<section>
<div class="container">
<div class="row" style="min-height: 700px">
<div class="col-md d-flex justify-content-center align-items-center">
<div class="card text-center">
<div class="card-header">
<ul class="nav nav-tabs card-header-tabs">
<li class="nav-item">
<a class="nav-link active" href="#">Log In</a>
</li>
</ul>
</div>
<div class="card-body">
<form @submit.prevent="login">
<div class="form-group text-left">
<label for="exampleInputEmail1">Email address</label>
<input
type="email"
class="form-control"
v-model="email"
aria-describedby="emailHelp"
/>
<small id="emailHelp" class="form-text text-muted"
>We'll never share your email with anyone else.</small
>
</div>
<div class="form-group text-left">
<label for="exampleInputPassword1">Password</label>
<input
type="password"
class="form-control"
v-model="password"
/>
</div>
<div class="text-left">
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</form>
<br />
<p class="text-left">Or continue with Google</p>
<GoogleLogin
:params="params"
:renderParams="renderParams"
:onSuccess="onSuccess"
></GoogleLogin>
<!-- <div class="g-signin2" data-onsuccess="onSignIn"></div> -->
</div>
</div>
</div>
</div>
</div>
</section>
</template>

<script>
import GoogleLogin from 'vue-google-login'
export default {
name: 'LoginPage',
components: {
GoogleLogin
},
data () {
return {
email: '',
password: '',
params: {
client_id:
'1048832564850-fpn38itn9av9bci2rfeoedhnih2sqnsi.apps.googleusercontent.com'
},
// only needed if you want to render the button with the google ui
renderParams: {
width: 250,
height: 50,
longtitle: true
}
}
},
methods: {
onSuccess (googleUser) {
const googleToken = googleUser.getAuthResponse().id_token
console.log(googleToken)
this.$store.dispatch('loginGoogle', googleToken)
.then(response => {
localStorage.setItem('access_token', response.data.access_token)
this.$router.push({ name: 'Home' })
this.$store.dispatch('fetchProducts')
})
.catch(err => {
console.log(err)
})
},
login () {
const data = {
email: this.email,
password: this.password
}
this.$store.dispatch('login', data)
}
}
}
</script>

<style>
</style>
22 changes: 22 additions & 0 deletions src/views/MainPage.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<template>
<div>
<div id="nav">
<Navbar></Navbar>
</div>
<router-view />
</div>
</template>

<script>
import Navbar from '../components/Navbar'
export default {
name: 'MainPage',
components: {
Navbar
}
}
</script>

<style>
</style>
16 changes: 16 additions & 0 deletions src/views/PageNotFound.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<template>
<div>
<h1>404</h1>
<h1>Page not found</h1>
</div>
</template>

<script>
export default {
}
</script>

<style>
</style>