ali 2 роки тому
батько
коміт
f8f519061a

+ 3 - 0
quasar.config.js

@@ -84,6 +84,9 @@ module.exports = configure(function (ctx) {
       transpileDependencies: [
         'js-abstract-model',
         'quasar-template-builder',
+        'quasar-app-extension-q-page-builder',
+        'quasar-crud',
+        'quasar-form-builder',
         'quasar-ui-q-page-builder'
       ],
 

+ 39 - 0
src/api/models/BasketItem.js

@@ -7,6 +7,7 @@ export default class BasketItemAPI extends APIRepository {
     super('package', appApi)
     this.APIAdresses = {
       base: '/store/items',
+      decrement: '/store/items/decrement',
       byId: (id) => '/store/items/' + id
     }
     this.CacheList = {
@@ -111,6 +112,44 @@ export default class BasketItemAPI extends APIRepository {
     })
   }
 
+  decrementPackage (packageId) {
+    return this.sendRequest({
+      apiMethod: 'post',
+      api: this.api,
+      request: this.APIAdresses.decrement,
+      data: this.getNormalizedSendData({
+        package: null // Number
+      }, {
+        package: packageId
+      }),
+      resolveCallback: (response) => {
+        return new BasketItem(response.data)
+      },
+      rejectCallback: (error) => {
+        return error
+      }
+    })
+  }
+
+  decrementProduct (productId) {
+    return this.sendRequest({
+      apiMethod: 'post',
+      api: this.api,
+      request: this.APIAdresses.decrement,
+      data: this.getNormalizedSendData({
+        product: null // Number
+      }, {
+        product: productId
+      }),
+      resolveCallback: (response) => {
+        return new BasketItem(response.data)
+      },
+      rejectCallback: (error) => {
+        return error
+      }
+    })
+  }
+
   update (basketItemId, count) {
     return this.sendRequest({
       apiMethod: 'put',

+ 0 - 297
src/components/ContentVideoPlayer.vue

@@ -1,297 +0,0 @@
-<template>
-  <div ref="videoPlayerWrapper"
-       class="vPlayer">
-    <video-player v-if="content.photo && content.isVideo() && content.hasVideoSource()"
-                  ref="videoPlayer"
-                  :key="playerKey"
-                  :source="content.getVideoSource()"
-                  :poster="content.photo"
-                  :over-player="hasTimepoint"
-                  :over-player-width="'250px'"
-                  :has-vast="canInitVAST && contentHasVast"
-                  :use-over-player="hasTimepoint"
-                  @adStarted="adStarted">
-      <template #overPlayer>
-        <div class="timepoint-list">
-          <q-banner class="timepoint-list-title">
-            زمان کوب ها
-            ({{ currentContent.timepoints.list.length }})
-          </q-banner>
-          <q-list class="timepoint-list-items">
-            <q-item v-for="timepoint in currentContent.timepoints.list"
-                    :key="timepoint.id"
-                    v-ripple
-                    clickable
-                    @click="goToTimpoint(timepoint)">
-              <q-item-section avatar>
-                <bookmark color="white"
-                          size="30"
-                          :is-favored="timepoint.is_favored"
-                          :loading="timepoint.loading"
-                          @clicked="handleTimepointBookmark(timepoint.id)" />
-              </q-item-section>
-              <q-item-section class="text-section">
-                <span>{{ timepoint.title }}</span>
-                <span v-if="currentContent.can_user_use_timepoint">
-                  {{ timepoint.formattedTime() }}
-                </span>
-              </q-item-section>
-            </q-item>
-          </q-list>
-        </div>
-      </template>
-    </video-player>
-    <div v-else>
-      <q-card class="flex justify-center items-center">
-        <q-img v-if="content.photo"
-               :src="content.photo" />
-        <div v-else
-             class="flex justify-center items-center"
-             style="height: 420px; justify-content: center">
-          ویدیویی وجود ندارد!
-        </div>
-      </q-card>
-    </div>
-  </div>
-</template>
-
-<script>
-import { Content } from 'src/models/Content.js'
-import Bookmark from 'src/components/Bookmark.vue'
-import VideoPlayer from 'src/components/VideoPlayer.vue'
-import TimeElapsedSinceLastEvent from 'src/assets/js/TimeElapsedSinceLastEvent.js'
-
-export default {
-  name: 'ContentVideoPlayer',
-  components: { VideoPlayer, Bookmark },
-  props: {
-    content: {
-      type: Content
-    },
-    showTimePoints: {
-      type: Boolean,
-      default: true
-    },
-    poster: {
-      type: String,
-      default () {
-        return ''
-      }
-    },
-    keepCalculating: {
-      type: Boolean,
-      default () {
-        return true
-      }
-    },
-    currentTimed: {
-      type: Number
-    }
-  },
-  emits: ['seeked'],
-  data() {
-    return {
-      canInitVAST: false,
-      playerKey: Date.now(),
-      currentContent: new Content()
-    }
-  },
-  computed: {
-    contentHasVast () {
-      return this.content.has_vast
-    },
-    hasTimepoint () {
-      return this.content.timepoints.list.length > 0
-    }
-  },
-  watch: {
-    content: {
-      handler (newVal) {
-        this.playerKey = Date.now()
-        this.currentContent = newVal
-        if (!this.currentContent.can_user_use_timepoint) {
-          this.currentContent.timepoints.removeAllTimes()
-        }
-      },
-      immediate: true,
-      deep: true
-    }
-  },
-  beforeUnmount() {
-    if (this.player) {
-      this.player.dispose()
-    }
-  },
-  beforeMount () {
-    this.canInitVAST = TimeElapsedSinceLastEvent.canInitVAST()
-  },
-  methods: {
-    adStarted () {
-      TimeElapsedSinceLastEvent.setEventOccurrenceTime()
-    },
-    getCurrentContentTimepoint (timepointId) {
-      return this.currentContent.timepoints.list.find(item => item.id === timepointId)
-    },
-    handleTimepointBookmark (timepointId) {
-      const timepointIndex = this.currentContent.timepoints.list.findIndex(item => item.id === timepointId)
-      const currentContentTimepoint = this.getCurrentContentTimepoint(timepointId)
-      this.currentContent.timepoints.list[timepointIndex].loading = true
-      const isFavoredStatus = currentContentTimepoint.is_favored ? 'unfavored' : 'favored'
-      this.changeTimepointStatus({
-        timepointId,
-        isFavoredStatus,
-        timepointIndex,
-        currentContentTimepoint
-      })
-    },
-    changeTimepointStatus (data) {
-      this.$apiGateway.content.setBookmarkTimepointFavoredStatus({
-        id: data.timepointId,
-        status: data.isFavoredStatus
-      })
-        .then(() => {
-          this.currentContent.timepoints.list[data.timepointIndex].is_favored = !data.currentContentTimepoint.is_favored
-          this.toggleFavorite(this.content.id)
-          this.currentContent.timepoints.list[data.timepointIndex].loading = false
-        })
-        .catch(() => {
-          this.currentContent.timepoints.list[data.timepointIndex].loading = false
-        })
-    },
-    goToTimpoint (timepoint) {
-      if (!this.$refs.videoPlayer) {
-        return
-      }
-      if (!this.currentContent.can_user_use_timepoint) {
-        this.$refs.videoPlayer.toggleFullScreen()
-        this.$q.dialog({
-          title: 'استفاده از زمان کوب',
-          message: 'جهت استفاده از زمان کوب می بایست اشتراک خریداری کنید.',
-          cancel: true,
-          persistent: true
-        }).onOk(() => {
-          this.$router.push({ name: 'Public.Landing.DynamicName', params: { landing_name: 'timepoint' } })
-        }).onCancel(() => {
-          // this.$router.push({ name: 'Public.Home' })
-        })
-        return
-      }
-      this.$refs.videoPlayer.changeCurrentTime(timepoint.time)
-    },
-    activate(time) {
-      this.player.currentTime(time)
-      this.player.play()
-      const requiredElement = document.querySelector('.video-js')
-      requiredElement.focus()
-    },
-    setSources() {
-      this.options.sources = this.source
-    },
-    setPoster() {
-      this.options.poster = this.poster
-    },
-    reInitVideo() {
-      this.player.src(this.source)
-      this.player.poster(this.poster)
-    },
-    toggleFavorite(id) {
-      const that = this
-      let count = -1
-      // let currentTimepointIndex = null
-      this.timePoints.forEach(function (item) {
-        count++
-        if (parseInt(item.id) === parseInt(id)) {
-          // currentTimepointIndex = index
-          item.loading = true
-          item.is_favored = !item.is_favored
-          that.postIsFavored = {
-            id: item.id,
-            isFavored: item.is_favored,
-            numberOfTimestamp: count
-          }
-        }
-      })
-      const requiredElement = document.querySelector('.video-js')
-      requiredElement.focus()
-      this.$emit('toggleBookmark', this.postIsFavored)
-      // setTimeout(function() { that.timePoints[currentTimepointIndex].loading = false }, 200) // vue/no-mutating-props
-    },
-    // postIsFavored(timeStampData){
-    //     var postStatus = 'unfavored'
-    //     if (timeStampData.isFavored){
-    //         postStatus = 'favored'
-    //     }
-    //     // /api/v2/timepoint/{timepoint_id}/favored
-    //     axios.post('/api/v2/c/timepoint/' + parseInt(timeStampData.id) + '/'+ postStatus)
-    //         .then(response => {
-    //             if (response.status === 200){
-    //                 this.timePoints.forEach( function (item) {
-    //                     if (parseInt(item.id) === parseInt(timeStampData.id)) {
-    //                         item.loading = false
-    //                     }
-    //                 })
-    //             }
-    //         })
-    //         .catch(err => console.error(err));
-    // },
-    calcWatchedPercentage(currentTime, duration) {
-      const watchedPercentage = ((currentTime / duration) * 100)
-      const videoPlayerTimeData = {
-        currentTime,
-        duration,
-        watchedPercentage
-      }
-      this.$emit('calcTimeData', videoPlayerTimeData)
-    },
-    videoStatus(val) {
-      this.videoIsPlaying = val
-    }
-  }
-}
-</script>
-
-<style scoped lang="scss">
-.vPlayer {
-  width: 100%;
-  .timepoint-list {
-    direction: ltr;
-    width: 100%;
-    color: white;
-    height: 100%;
-    padding-bottom: 30px;
-    background: rgba(0,0,0,0.4);
-    .timepoint-list-title {
-      text-align: center;
-      background: rgba(0,0,0,0.7);
-    }
-    .timepoint-list-items {
-      .text-section {
-        display: flex;
-        flex-flow: row;
-        font-size: 0.7rem;
-        font-weight: bold;
-        align-items: center;
-        justify-content: space-between;
-      }
-    }
-    :deep(.q-list) {
-      height: calc(100% - 54px);
-      overflow: auto;
-      .bookmark-btn.q-btn {
-        width: 26px;
-        height: 26px;
-        padding: 0;
-        font-size: 10px;
-        color: $primary !important;
-        .q-btn__content {
-          margin: 3px;
-          svg {
-            width: 20px;
-            height: 20px;
-          }
-        }
-      }
-    }
-  }
-}
-</style>

+ 0 - 829
src/components/VideoPlayer.vue

@@ -1,829 +0,0 @@
-<template>
-  <div ref="videoPlayerWrapper"
-       class="vPlayer">
-    <video ref="videoPlayer"
-           dir="ltr"
-           class="video-js vjs-fluid vjs-big-play-centered vjs-show-big-play-button-on-pause"
-           controls
-           preload="none">
-      <p class="vjs-no-js">
-        To view this video please enable JavaScript, and consider upgrading to a web browser that
-        <a href="https://videojs.com/html5-video-support/"
-           target="_blank">
-          supports HTML5 video
-        </a>
-      </p>
-    </video>
-
-    <div class="VastElements">
-      <div ref="VastTimerBtn"
-           class="VastElement VastTimerBtn hide" />
-      <div ref="VastSkipAdBtn"
-           class="VastElement VastSkipAdBtn hide">
-        رد کردن
-      </div>
-      <div ref="VastLinkBtn"
-           class="VastElement VastLinkBtn hide">
-        اطلاعات بیشتر
-      </div>
-    </div>
-
-    <div v-if="useOverPlayer && !isInVastMode"
-         ref="overPlayer"
-         class="over-player-wrapper">
-      <q-btn icon-right="isax:menu-1"
-             size="sm"
-             color="primary"
-             class="toggleSideBarBtn"
-             @click="toggleSideBar" />
-      <div class="over-player-slot"
-           :class="{'show': localOverPlayer, 'hide': !localOverPlayer}"
-           :style="{width: overPlayerWidth}">
-        <slot name="overPlayer" />
-      </div>
-    </div>
-  </div>
-</template>
-
-<script>
-import videojs from 'video.js'
-import 'videojs-hls-quality-selector'
-import videojsBrand from 'videojs-brand'
-import fa from 'video.js/dist/lang/fa.json'
-import { APIGateway } from 'src/api/APIGateway.js'
-// import { mixinAbrisham } from 'src/mixin/Mixins.js'
-import { PlayerSourceList } from 'src/models/PlayerSource.js'
-import videoJsResolutionSwitcher from 'src/assets/js/videoJsResolutionSwitcher.js'
-
-// https://cph-p2p-msl.akamaized.net/hls/live/2000341/test/master.m3u8 (Live)
-// https://bitdash-a.akamaihd.net/content/MI201109210084_1/m3u8s/f08e80da-bf1d-4e3d-8899-f0f6155f6efa.m3u8
-
-export default {
-  name: 'VideoPlayer',
-  // mixins: [mixinAbrisham],
-  props: {
-    hasVast: {
-      type: Boolean,
-      default: false
-    },
-    source: {
-      type: [String, PlayerSourceList],
-      default: null
-    },
-    useOverPlayer: {
-      type: Boolean,
-      default: false
-    },
-    poster: {
-      type: String,
-      default () {
-        return ''
-      }
-    },
-    overPlayer: {
-      type: Boolean,
-      default: true
-    },
-    overPlayerWidth: {
-      type: String,
-      default: '100%'
-    },
-    keepCalculating: {
-      type: Boolean,
-      default () {
-        return true
-      }
-    },
-    bookmarkLoading: {
-      type: Boolean,
-      default () {
-        return false
-      }
-    },
-    currentTimed: {
-      type: Number
-    }
-  },
-  emits: ['seeked', 'adStarted', 'update:sideBar'],
-  data() {
-    return {
-      needReInitVideo: false,
-      isInVastMode: false,
-      vastSrc: null,
-      vastLink: null,
-      vastSkipOffset: null,
-      vastStartOffset: null,
-      vastTimerInterval: null,
-      width: '',
-      showVastElements: false,
-      drawer: false,
-      player: null,
-      localOverPlayer: true,
-      favLoading: false,
-      options: {
-        myItems: [{
-          name: 'test'
-        }],
-        controlBar: {
-          // currentTimeDisplay: true,
-          TimeDivider: true,
-          children: [
-            'playToggle',
-            'PlaybackRateMenuButton',
-            'CurrentTimeDisplay',
-            'progressControl',
-            'TimeDivider',
-            'RemainingTimeDisplay',
-            'volumePanel',
-            'SubtitlesButton',
-            'QualitySelector',
-            'fullscreenToggle',
-            'PictureInPictureToggle'
-          ],
-          volumePanel: {
-            inline: true,
-            vertical: true
-          }
-        },
-        language: 'fa',
-        languages: {
-          fa
-        },
-        responsive: true,
-        autoplay: false,
-        controls: true,
-        playbackRates: [0.5, 1, 1.5, 2, 3, 4],
-        nativeControlsForTouch: false,
-        sources: [],
-        poster: null,
-        plugins: {
-          // hotkeys: {
-          //   enableModifiersForNumbers: false,
-          //   seekStep: 5,
-          //   enableMute: true,
-          //   enableVolumeScroll: true,
-          //   enableHoverScroll: true,
-          //   enableFullscreen: true
-          // }
-        }
-      },
-      videoIsPlaying: false,
-      postIsFavored: {}
-    }
-  },
-  computed: {
-    calcTheHeight() {
-      return '100%'
-    },
-    calcTheWidth() {
-      return '100%'
-    },
-    currentTime() {
-      return this.currentTimed
-    }
-  },
-  watch: {
-    source: {
-      handler () {
-        if (typeof window === 'undefined') {
-          return
-        }
-        this.$nextTick(() => {
-          if (this.$refs.videoPlayer) {
-            this.reInitVideo()
-          } else {
-            this.needReInitVideo = true
-          }
-        })
-      },
-      immediate: true
-    },
-    currentTime(time) {
-      this.player.currentTime(time)
-    },
-    overPlayer(newValue) {
-      this.localOverPlayer = newValue
-    },
-    useOverPlayer (newValue) {
-      if (!newValue) {
-        return
-      }
-
-      this.$nextTick(() => {
-        this.moveSideBarElementIntoVideoPlayerElements()
-      })
-    }
-  },
-  created() {
-    this.width = this.overPlayerWidth
-    this.setPoster()
-    this.setSources()
-  },
-  mounted() {
-    if (this.needReInitVideo) {
-      this.$nextTick(() => {
-        this.reInitVideo()
-      })
-    }
-    if (this.useOverPlayer) {
-      this.$nextTick(() => {
-        this.moveSideBarElementIntoVideoPlayerElements()
-      })
-    }
-  },
-  beforeUnmount() {
-    if (this.player) {
-      this.player.dispose()
-    }
-  },
-  methods: {
-    getVast () {
-      return APIGateway.vast.getXml()
-        .then((vastXml) => {
-          this.loadVast(vastXml)
-        })
-        .catch(() => {
-        })
-    },
-    showVastElement (vastClassName) {
-      this.removeVastClass(vastClassName, 'hide')
-      this.addVastClass(vastClassName, 'show')
-    },
-    hideVastElement (vastClassName) {
-      this.removeVastClass(vastClassName, 'show')
-      this.addVastClass(vastClassName, 'hide')
-    },
-    addVastClass (vastClassName, classValue) {
-      const vastElement = this.vastElementExist(vastClassName)
-      if (!vastElement) {
-        return false
-      }
-
-      vastElement.classList.add(classValue)
-    },
-    removeVastClass (vastClassName, classValue) {
-      const vastElement = this.vastElementExist(vastClassName)
-      if (!vastElement) {
-        return false
-      }
-
-      vastElement.classList.remove(classValue)
-    },
-    vastElementExist (vastClassName) {
-      const vastElement = this.$refs.videoPlayerWrapper.getElementsByClassName(vastClassName)[0]
-      if (!vastElement) {
-        return false
-      }
-
-      return vastElement
-    },
-    loadVastDomElements () {
-      this.injectDomeElement(this.$refs.VastTimerBtn)
-      this.injectDomeElement(this.$refs.VastSkipAdBtn)
-      this.injectDomeElement(this.$refs.VastLinkBtn)
-    },
-    showVastLinkBtn (link, title = 'اطلاعات بیشتر') {
-      this.showVastElement('VastLinkBtn')
-      this.updateVastElementInnerHTML('VastLinkBtn', '<a href="' + link + '" target="_blank">' + title + '</a>')
-    },
-    stopVastTimer () {
-      if (this.vastTimerInterval) {
-        clearInterval(this.vastTimerInterval)
-      }
-      this.updateVastTimer(0)
-      this.hideVastElement('VastTimerBtn')
-    },
-    startVastTimer (endTimerCallback) {
-      this.showVastElement('VastTimerBtn')
-      if (this.vastTimerInterval) {
-        clearInterval(this.vastTimerInterval)
-      }
-      let seconds = this.getVastTimerSeconds()
-      if (seconds === 0) {
-        this.stopVastTimer()
-        return
-      }
-      this.updateVastTimer(seconds--)
-      this.vastTimerInterval = setInterval(() => {
-        this.updateVastTimer(seconds--)
-        if (seconds < 0) {
-          this.stopVastTimer()
-          endTimerCallback()
-        }
-      }, 1000)
-    },
-    getVastTimerSeconds () {
-      if (!this.vastSkipOffset) {
-        return 0
-      }
-
-      const array = this.vastSkipOffset.split(':')
-      return (array[0] * 60 * 60) + (array[1] * 60) + array[2]
-    },
-    updateVastTimer (timer) {
-      this.updateVastElementInnerHTML('VastTimerBtn', timer + ' ثانیه')
-    },
-    updateVastElementInnerHTML (vastClassName, innerHTML) {
-      const vastElement = this.vastElementExist(vastClassName)
-      if (!vastElement) {
-        return false
-      }
-
-      vastElement.innerHTML = innerHTML
-    },
-    sowVastSkipAdBtn () {
-      const vastClassName = 'VastSkipAdBtn'
-      this.showVastElement(vastClassName)
-      const vastElement = this.vastElementExist(vastClassName)
-      if (!vastElement) {
-        return false
-      }
-      vastElement.addEventListener('click', (event) => {
-        this.endVast()
-      })
-    },
-    loadVast (vastXml) {
-      const parser = new DOMParser()
-      const xmlDoc = parser.parseFromString(vastXml, 'text/xml')
-      this.vastSrc = xmlDoc.getElementsByTagName('MediaFile')[0].childNodes[0].nodeValue
-      this.vastLink = xmlDoc.getElementsByTagName('ClickThrough')[0].childNodes[0].nodeValue
-      this.vastStartOffset = xmlDoc.getElementsByTagName('Linear')[0].getAttribute('startoffset')
-      this.vastSkipOffset = xmlDoc.getElementsByTagName('Linear')[0].getAttribute('skipoffset')
-
-      this.player.addClass('vjs-ad-playing')
-
-      setTimeout(() => {
-        this.isInVastMode = true
-        const playerSourceList = new PlayerSourceList([{
-          src: this.vastSrc,
-          type: 'video/mp4',
-          label: 'کیفیت عالی',
-          caption: 'کیفیت عالی',
-          link: this.vastSrc,
-          res: 720,
-          selected: false
-        }])
-        const source = this.isPlayerSourceList() ? playerSourceList.list : this.vastSrc
-        this.player.src(source)
-      }, 100)
-
-      this.player.one('play', () => {
-        this.player.controlBar.progressControl.disable()
-        this.loadVastDomElements()
-
-        this.showVastLinkBtn(this.vastLink)
-        this.startVastTimer(() => {
-          this.sowVastSkipAdBtn()
-        })
-
-        this.$emit('adStarted')
-      })
-      this.player.one('ended', () => {
-        if (!this.isInVastMode) {
-          return
-        }
-        this.endVast()
-      })
-    },
-    startVast (vastXml) {
-      const parser = new DOMParser()
-      const xmlDoc = parser.parseFromString(vastXml, 'text/xml')
-      this.vastSrc = xmlDoc.getElementsByTagName('MediaFile')[0].childNodes[0].nodeValue
-      this.vastLink = xmlDoc.getElementsByTagName('ClickThrough')[0].childNodes[0].nodeValue
-      this.vastStartOffset = xmlDoc.getElementsByTagName('Linear')[0].getAttribute('startoffset')
-      this.vastSkipOffset = xmlDoc.getElementsByTagName('Linear')[0].getAttribute('skipoffset')
-
-      // in a real plugin, you might fetch ad inventory here
-      this.player.trigger('adsready')
-    },
-    endVast () {
-      this.isInVastMode = false
-      this.player.controlBar.progressControl.enable()
-      this.hideVastElement('VastTimerBtn')
-      this.hideVastElement('VastSkipAdBtn')
-      this.hideVastElement('VastLinkBtn')
-      this.player.removeClass('vjs-ad-playing')
-      // setTimeout(() => {
-      this.setPoster()
-      this.setSources()
-      const source = this.isPlayerSourceList() ? this.source.list : this.source
-      this.player.src(source)
-      this.player.poster(this.poster)
-      // this.player.reset()
-      this.player.play()
-      // }, 100)
-      this.$emit('adEnded')
-    },
-
-    focusOnPlayer () {
-      this.player.el().focus()
-    },
-    setPlayerBrand () {
-      this.player.brand({
-        image: 'https://nodes.alaatv.com/upload/landing/chatr/alaa%20logo.png?w=30&h=30',
-        title: 'پالیزوانی',
-        destination: '/',
-        destinationTarget: '_blank'
-      })
-    },
-    redefineTap () {
-      this.player.on('touchend', function(e) { // tap
-        if (this.player().controls()) {
-          const classes = [
-            'vjs-tech'
-          ]
-          const canDoAction = !classes.find(className => e.target.classList.contains(className))
-          if (canDoAction) {
-            return
-          }
-          if (this.player().paused()) {
-            this.player().play()
-          } else {
-            this.player().pause()
-          }
-        }
-      })
-    },
-    hasPlugin (pluginName) {
-      return Object.keys(videojs.getPlugins()).includes(pluginName)
-    },
-    initPlayer (withVast) {
-      if (!this.hasPlugin('brand')) {
-        videojs.registerPlugin('brand', videojsBrand)
-      }
-      // if (this.isPlayerSourceList(this.source) && !this.hasPlugin('videoJsResolutionSwitcher')) { // old multiple quality type
-      if (this.isPlayerSourceList(this.source)) { // old multiple quality type
-        videoJsResolutionSwitcher(videojs)
-        this.options.plugins.videoJsResolutionSwitcher = {
-          default: 'کیفیت بالا',
-          dynamicLabel: true
-        }
-      }
-
-      this.player = videojs(this.$refs.videoPlayer, this.options)
-      if (this.hasVast && (typeof withVast === 'undefined' || withVast === true)) {
-        // this.loadVast()
-        this.getVast()
-      }
-
-      this.player.ready(() => {
-        this.setPlayerBrand()
-        this.focusOnPlayer()
-        this.redefineTap()
-        // this.on('timeupdate', function () {
-        //   if (that.keepCalculating) {
-        //     that.calcWatchedPercentage(this.currentTime(), this.duration())
-        //   }
-        //   // document.querySelector('.video-js').focus()
-        //   if (that.$refs.videoPlayer) {
-        //     that.$refs.videoPlayer.focus()
-        //   }
-
-        //   if (!that.player.paused() && !that.player.userActive()) {
-        //     that.videoStatus(false)
-        //   } else if (!that.player.paused()) {
-        //     that.videoStatus(true)
-        //   }
-        // })
-      })
-
-      if (typeof this.source === 'string' && this.source.includes('.m3u8')) { // hls type
-        this.player.hlsQualitySelector()
-      }
-
-      this.player.on('seeked', () => {
-        this.$emit('seeked', this.player.currentTime())
-      })
-
-      // const events = [
-      //   'loadstart',
-      //   'progress',
-      //   'suspend',
-      //   'abort',
-      //   'error',
-      //   'emptied',
-      //   'stalled',
-      //   'loadedmetadata',
-      //   'loadeddata',
-      //   'canplay',
-      //   'canplaythrough',
-      //   'playing',
-      //   'waiting',
-      //   'seeking',
-      //   'seeked',
-      //   'ended',
-      //   'durationchange',
-      //   'timeupdate',
-      //   'play',
-      //   'pause',
-      //   'ratechange',
-      //   'resize',
-      //   'volumechange'
-      // ]
-      //
-      // events.forEach(event => {
-      //   this.player.on(event, () => {
-      //     console.log('event: ', event)
-      //     console.log('sources: ', this.player.sources)
-      //   })
-      // })
-    },
-    changeCurrentTime (time) {
-      if (!this.player) {
-        return
-      }
-      this.player.currentTime(time)
-    },
-    injectDomeElement (element) {
-      this.$refs.videoPlayerWrapper.querySelector('.video-js').appendChild(element)
-    },
-    createDomeElement (elementClass, refKey) {
-      const div = document.createElement('div')
-      div.classList = elementClass
-      const child = this.$refs[refKey].$el ? this.$refs[refKey].$el : this.$refs[refKey]
-      div.appendChild(child)
-      this.$refs.videoPlayerWrapper.querySelector('.video-js').appendChild(div)
-    },
-    moveSideBarElementIntoVideoPlayerElements () {
-      this.createDomeElement('over-player-wrapper-div', 'overPlayer')
-    },
-    toggleSideBar () {
-      this.localOverPlayer = !this.localOverPlayer
-      // this.$emit('update:sideBar', this.localOverPlayer)
-    },
-    activate(time) {
-      this.player.currentTime(time)
-      this.player.play()
-      const requiredElement = document.querySelector('.video-js')
-      requiredElement.focus()
-    },
-    setSources(sources) {
-      this.options.sources = sources || (this.isPlayerSourceList() ? this.source.list : this.source)
-    },
-    setPoster(poster) {
-      this.options.poster = poster || this.poster
-    },
-    reInitVideo(withVast) {
-      if (this.player?.reset) {
-        this.player.reset()
-      }
-      // this.player.dispose()
-      this.setPoster()
-      this.setSources()
-      this.initPlayer(withVast)
-      const source = this.isPlayerSourceList() ? this.source.list : this.source
-      this.player.src(source)
-      this.player.poster(this.poster)
-    },
-    isPlayerSourceList () {
-      return (this.source && this.source.list && Array.isArray(this.source.list))
-    },
-    calcWatchedPercentage(currentTime, duration) {
-      const watchedPercentage = ((currentTime / duration) * 100)
-      const videoPlayerTimeData = {
-        currentTime,
-        duration,
-        watchedPercentage
-      }
-      this.$emit('calcTimeData', videoPlayerTimeData)
-    },
-    videoStatus(val) {
-      this.videoIsPlaying = val
-    },
-    toggleFullScreen() {
-      if (document.exitFullscreen) {
-        document.exitFullscreen()
-      } else if (document.webkitExitFullscreen) { /* Safari */
-        document.webkitExitFullscreen()
-      } else if (document.msExitFullscreen) { /* IE11 */
-        document.msExitFullscreen()
-      }
-    }
-  }
-}
-</script>
-
-<style lang="scss">
-@import "video.js/dist/video-js.css";
-.vPlayer {
-  width: 100%;
-  overflow: hidden;
-  display: contents;
-  .VastElements {
-    display: none;
-  }
-  .VastElement {
-    position: absolute;
-    bottom: 60px;
-    height: 50px;
-    border: solid 2px $primary;
-    border-radius: 8px;
-    background: rgba(0, 0, 0, 0.3);
-    display: flex;
-    flex-flow: row;
-    justify-content: center;
-    align-items: center;
-    cursor: pointer;
-    transition: all 1s;
-    $width: 150px;
-    &.VastTimerBtn {
-      border-top-right-radius: 0;
-      border-bottom-right-radius: 0;
-      border-right: none;
-      width: $width;
-      cursor: not-allowed;
-      &.show {
-        right: 0;
-      }
-      &.hide {
-        right: -$width;
-      }
-    }
-    &.VastSkipAdBtn {
-      right: 0;
-      border-top-right-radius: 0;
-      border-bottom-right-radius: 0;
-      border-right: none;
-      width: $width;
-      color: white;
-      display: flex;
-      align-items: center;
-      justify-content: center;
-      font-size: 0.8rem;
-      &.show {
-        right: 0;
-      }
-      &.hide {
-        right: -$width;
-      }
-    }
-    &.VastLinkBtn {
-      left: 0;
-      border-top-left-radius: 0;
-      border-bottom-left-radius: 0;
-      border-left: none;
-      width: $width;
-      &.show {
-        left: 0;
-      }
-      &.hide {
-        left: -$width;
-      }
-      a {
-        width: 100%;
-        height: 100%;
-        color: white;
-        display: flex;
-        align-items: center;
-        justify-content: center;
-        font-size: 0.8rem;
-      }
-    }
-    @media screen and (max-width: 600px) {
-      height: 35px;
-      $screen-max-600-width: 100px;
-      &.VastTimerBtn {
-        width: $screen-max-600-width;
-        font-size: 0.7rem;
-        &.hide {
-          left: -$screen-max-600-width;
-        }
-      }
-      &.VastSkipAdBtn {
-        width: $screen-max-600-width;
-        font-size: 0.7rem;
-        &.hide {
-          left: -$screen-max-600-width;
-        }
-      }
-      &.VastLinkBtn {
-        width: $screen-max-600-width;
-        &.hide {
-          right: -$screen-max-600-width;
-        }
-        a {
-          font-size: 0.7rem;
-        }
-      }
-    }
-
-  }
-  .over-player-wrapper-div {
-    position: absolute;
-    top: 0;
-    left: 0;
-    height: 100%;
-    z-index: 1;
-    .over-player-wrapper {
-      position: absolute;
-      top: 0;
-      left: 0;
-      height: 100%;
-      .over-player-slot {
-        left: 2000px;
-        height: 100%;
-        color: initial;
-        transition: 0.4s;
-        direction: ltr;
-        position: relative;
-        z-index: 1;
-        &.show {
-          right: 0;
-        }
-        &.hide {
-          width: 0 !important;
-          right: 2500px;
-        }
-      }
-    }
-  }
-  .toggleSideBarBtn {
-    position: absolute;
-    top: 5px;
-    left: 5px;
-    z-index: 2;
-  }
-  .video-js {
-    overflow: hidden;
-    background-color: transparent;
-    &.vjs-ad-playing {
-      .vjs-resolution-button {
-        display: none;
-      }
-    }
-    .vjs-loading-spinner {
-      right: 50%;
-      margin: -25px -25px 0 0;
-      text-align: right;
-    }
-    .vjs-big-play-button {
-      color: white;
-      width: 80px;
-      height: 80px;
-      border-radius: 100%;
-      background: $primary;
-      border-color: $primary;
-
-      @media screen and (max-width: 600px) {
-        margin-top: -60px;
-      }
-      .vjs-icon-placeholder:before {
-        display: flex;
-        font-size: 65px;
-        align-items: center;
-        justify-content: center;
-      }
-    }
-    .vjs-control-bar {
-      z-index: 2;
-      .vjs-volume-panel {
-        .vjs-volume-control {
-          // right: -3.5em;
-          // margin-right: -1px;
-        }
-      }
-      .vjs-resolution-button {
-        .vjs-menu-button {
-          .vjs-icon-placeholder {
-            &:before {
-              content: "\f114";
-              font-style: normal;
-              font-weight: normal;
-              font-family: VideoJS, Arial, serif;
-            }
-          }
-        }
-      }
-    }
-    .vjs-play-progress {
-      &:before {
-        /*rtl:ignore*/
-        right: -0.5em;
-        /*rtl:ignore*/
-        left: auto;
-      }
-    }
-    .vjs-volume-tooltip {
-      float: right;
-    }
-    .vjs-volume-level {
-      right: 0;
-    }
-  }
-}
-
-.vjs-slider-horizontal .vjs-volume-level:before {
-  left: 0;
-  right: auto;
-}
-.video-js .vjs-volume-level {
-    /* right: 0; */
-}
-.video-js .vjs-volume-tooltip {
-    /* float: left; */
-}
-.vPlayer .video-js .vjs-control-bar .vjs-volume-panel .vjs-volume-control {
-    /* left: -3.5em; */
-    /* margin-left: -1px; */
-}
-</style>

+ 2 - 2
src/components/Widgets/Store/Cart/components/step1/cartItem.vue

@@ -63,7 +63,7 @@
                  color="red"
                  icon="delete_outline"
                  :loading="basketItem.loading"
-                 @click="onDecrease" />
+                 @click="onRemove" />
         </div>
       </div>
       <div class="cart-item__price">
@@ -89,8 +89,8 @@
 </template>
 
 <script>
+import { Product } from 'src/models/Product.js'
 import { BasketItem } from 'src/models/BasketItem.js'
-import { Product } from 'src/models/Product'
 
 export default {
   name: 'cartItem',

+ 4 - 5
src/components/Widgets/Store/Cart/components/step1/step1.vue

@@ -68,8 +68,10 @@ export default {
     },
     onDecrease (basketItem) {
       basketItem.loading = true
-      const newCount = basketItem.count - 1
-      APIGateway.basketItem.update(basketItem.id, newCount)
+      const promise = basketItem.product ? APIGateway.basketItem.decrementProduct(basketItem.product) : APIGateway.basketItem.decrementPackage(basketItem.package)
+      // const newCount = basketItem.count - 1
+      // APIGateway.basketItem.update(basketItem.id, newCount)
+      promise
         .then(() => {
           this.checkoutReview()
         })
@@ -105,9 +107,6 @@ export default {
     display: flex;
     flex-flow: column;
     gap: 24px;
-    .step1__cart-item {
-
-    }
   }
   .step1__sidebar {
     width: $sidebar-width;

+ 138 - 0
src/components/Widgets/Store/Cart/components/step2/cartAddress.vue

@@ -0,0 +1,138 @@
+<template>
+  <div class="cart-address">
+    <div class="cart-address__title">
+      <q-icon name="place" />
+      انتخاب آدرس:
+    </div>
+    <div class="cart-address__info">
+      <div class="cart-address__selected-address">
+        <q-radio :label="basket.getFullAddressString()" />
+      </div>
+      <div class="cart-address__edit-btn">
+        <q-btn color="primary"
+               outline
+               @click="openDialog">
+          <q-icon name="edit" />
+          ویرایش
+        </q-btn>
+      </div>
+    </div>
+    <q-dialog v-model="dialog">
+      <q-card class="cart-address__dialog-cart">
+        <q-card-section class="cart-address__dialog-cart-title-section flex justify-between">
+          <div>
+            ویرایش آدرس
+          </div>
+          <q-btn v-close-popup
+                 icon="close"
+                 flat
+                 round
+                 dense />
+        </q-card-section>
+        <q-separator />
+        <q-card-section class="cart-address__dialog-cart-form-section">
+          <form-builder v-model:value="addressInputs"
+                        :loading="setAddressLoading" />
+          <div class="flex justify-end q-mt-lg">
+            <q-btn :loading="setAddressLoading"
+                   color="primary"
+                   @click="setAddress">
+              ویرایش و ثبت
+            </q-btn>
+          </div>
+        </q-card-section>
+      </q-card>
+    </q-dialog>
+  </div>
+</template>
+
+<script>
+import { Basket } from 'src/models/Basket.js'
+import { APIGateway } from 'src/api/APIGateway.js'
+import { FormBuilder, FormBuilderAssist } from 'quasar-form-builder'
+
+export default {
+  name: 'CartAddress',
+  components: { FormBuilder },
+  props: {
+    basket: {
+      type: Basket,
+      default: new Basket()
+    }
+  },
+  emits: ['set-address'],
+  data () {
+    return {
+      step: 1,
+      dialog: false,
+      setAddressLoading: false,
+      addressInputs: [
+        { type: 'input', name: 'province', responseKey: 'province', label: 'استان', placeholder: ' ', col: 'col-md-4 col-12' },
+        { type: 'input', name: 'city', responseKey: 'city', label: 'شهر', placeholder: ' ', col: 'col-md-4 col-12' },
+        { type: 'input', name: 'postal_code', responseKey: 'postal_code', label: 'کد پستی', placeholder: ' ', col: 'col-md-4 col-12' },
+        { type: 'input', name: 'address', responseKey: 'address', label: 'آدرس پستی', placeholder: ' ', col: 'col-12' }
+      ]
+    }
+  },
+  computed: {
+    addressString () {
+      return 'file_download'
+    }
+  },
+  mounted() {
+    this.setFormData()
+  },
+  methods: {
+    openDialog () {
+      this.dialog = true
+    },
+    closeDialog () {
+      this.dialog = false
+    },
+    setAddress () {
+      this.setAddressLoading = true
+      APIGateway.basket.setAddress(this.basket.id, this.getFormData())
+        .finally(() => {
+          this.setAddressLoading = false
+          this.closeDialog()
+          this.$emit('set-address')
+        })
+    },
+    getFormData () {
+      return FormBuilderAssist.getFormData(this.addressInputs)
+    },
+    setFormData () {
+      FormBuilderAssist.setInputValues(this.basket, this.addressInputs)
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.cart-address {
+  border-radius: 8px;
+  background: #F9F9FB;
+  border: 1px solid #DFE1EC;
+  padding: 20px 30px 46px 38px;
+  .cart-address__title {
+    color: #727272;
+    font-size: 16px;
+    margin-bottom: 22px;
+  }
+  .cart-address__info {
+    display: flex;
+    flex-flow: row;
+    justify-content: space-between;
+    .cart-address__selected-address {
+
+    }
+    .cart-address__edit-btn {
+
+    }
+  }
+  :global(.cart-address__dialog-cart) {
+    width: 700px;
+    max-width: 90vw;
+  }
+}
+</style>

+ 22 - 231
src/components/Widgets/Store/Cart/components/step2/cartItem.vue

@@ -3,97 +3,21 @@
     <div class="cart-item__use-type">
       <q-icon :name="usageIcon" />
     </div>
-    <div class="cart-item__remove-action">
-      <q-btn flat
-             icon="close"
-             @click="onRemove" />
-    </div>
     <div class="cart-item__thumbnail">
       <q-img :src="packageOrProductObject.thumbnail" />
     </div>
-    <div class="cart-item__info">
-      <div class="cart-item__title">
-        {{ packageOrProductObject.title }}
-      </div>
-      <div class="cart-item__attrs">
-        <div class="cart-item__attr">
-          <div class="cart-item__attr-label">
-            نوع:
-          </div>
-          <div class="cart-item__attr-value">
-            {{ packageOrProductObject.is_physical_info.label }}
-          </div>
-        </div>
-        <div v-if="basketItem.product"
-             class="cart-item__attr">
-          <div class="cart-item__attr-label">
-            جنس:
-          </div>
-          <div class="cart-item__attr-value">
-            {{ packageOrProductObject.physical_type_info.label }}
-          </div>
-        </div>
-      </div>
-      <div v-if="false"
-           class="cart-item__type-info">
-        صوت
-        <q-icon color="primary"
-                name="account_balance_wallet" />
-      </div>
-      <div class="cart-item__count-action">
-        <div class="cart-item__count-action-increase">
-          <q-btn outline
-                 color="primary"
-                 icon="add"
-                 :loading="basketItem.loading"
-                 @click="onIncrease" />
-        </div>
-        <div class="cart-item__count-action-count">
-          {{ basketItem.count }}
-        </div>
-        <div class="cart-item__count-action-decrease">
-          <q-btn v-if="basketItem.count > 1"
-                 outline
-                 color="red"
-                 icon="remove"
-                 :loading="basketItem.loading"
-                 @click="onDecrease" />
-          <q-btn v-else
-                 flat
-                 color="red"
-                 icon="delete_outline"
-                 :loading="basketItem.loading"
-                 @click="onDecrease" />
-        </div>
-      </div>
-      <div class="cart-item__price">
-        <div class="cart-item__price-base">
-          {{ packageOrProductObject.unit_price.toLocaleString('fa') }}
-          تومان
-        </div>
-        <div class="cart-item__price-final">
-          {{ packageOrProductObject.getFinalPrice() }}
-          تومان
-        </div>
-      </div>
-      <div v-if="basketItem.package"
-           class="cart-item__products">
-        <div v-for="(product, productIndex) in basketItem.package_info.products_info.list"
-             :key="productIndex"
-             class="cart-item__product">
-          {{ product.title }}
-        </div>
-      </div>
+    <div class="cart-item__title">
+      {{ packageOrProductObject.title }}
     </div>
   </div>
 </template>
 
 <script>
+import { Product } from 'src/models/Product.js'
 import { BasketItem } from 'src/models/BasketItem.js'
-import { Product } from 'src/models/Product'
 
 export default {
-  name: 'cartItem',
+  name: 'CartItem',
   props: {
     basketItem: {
       type: BasketItem,
@@ -143,166 +67,33 @@ export default {
 
 <style scoped lang="scss">
 .cart-item {
-  $cart-background-color: #F9F9FB;
-  $cart-item__thumbnail-width: 153px;
   position: relative;
-  background: $cart-background-color;
-  border: 1px solid #DFE1EC;
-  border-radius: 8px;
-  padding: 24px 22px 24px 67px;
-  display: flex;
-  flex-flow: row;
-  gap: 30px;
-  min-height: 203px;
   .cart-item__use-type {
-    $cart-item__use-type-width: 37px;
     position: absolute;
     top: 0;
-    left: 16px;
-    width: $cart-item__use-type-width;
-    color: white;
-    text-align: center;
-    padding-top: 10px;
-    padding-bottom: 22px;
-    background: $primary;
-    &:after {
-      position: absolute;
-      content: ' ';
-      bottom: calc( (#{$cart-item__use-type-width} * -1 / 2) - 9px );
-      left: 0;
-      transform: rotate(45deg);
-      background: $cart-background-color;
-      width: $cart-item__use-type-width;
-      height: $cart-item__use-type-width;
-      border-radius: 2px;
-    }
-  }
-  .cart-item__remove-action {
-    position: absolute;
-    top: 85px;
-    left: 20px;
-    .q-btn {
-      width: 32px;
-      height: 32px;
+    right: 0;
+    width: 41px;
+    height: 41px;
+    background: #C8FFCF;
+    border-radius: 0 8px;
+    opacity: 0.57;
+    color: $primary;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    z-index: 2;
+    .q-icon {
+      font-size: 25px;
     }
   }
   .cart-item__thumbnail {
-    width: $cart-item__thumbnail-width;
+    border-radius: 8px;
+    overflow: hidden;
   }
-  .cart-item__info {
-    width: calc( 100% - #{$cart-item__thumbnail-width} );
+  .cart-item__title {
+    font-size: 14px;
     color: #212121;
-    .cart-item__title {
-      font-size: 22px;
-      margin-bottom: 27px;
-    }
-    .cart-item__attrs {
-      display: flex;
-      flex-flow: row;
-      gap: 35px;
-      flex-wrap: nowrap;
-      margin-bottom: 70px;
-      .cart-item__attr {
-        display: flex;
-        flex-flow: row;
-        gap: 5px;
-        flex-wrap: nowrap;
-        .cart-item__attr-label {
-          font-size: 16px;
-          color: #797979;
-          &:before {
-            display: inline-block;
-            content: ' ';
-            width: 8px;
-            height: 8px;
-            background: #475F4A;
-            border-radius: 100%;
-            margin-right: 4px;
-          }
-        }
-        .cart-item__attr-value {
-          font-size: 16px;
-        }
-      }
-    }
-    .cart-item__type-info {
-      position: absolute;
-      top: 30px;
-      right: 22px;
-      display: flex;
-      flex-flow: row;
-      align-items: center;
-      justify-content: space-between;
-      gap: 2px;
-      padding: 0 4px 0 16px;
-      background: #FFFFFF;
-      border: 1px solid #DFE1EC;
-      border-radius: 20px;
-      color: #212121;
-      font-size: 16px;
-    }
-    .cart-item__count-action {
-      position: absolute;
-      right: 20px;
-      top: 65px;
-      background: #FFFFFF;
-      border: 1px solid #DFE1EC;
-      border-radius: 8px;
-      padding: 12px 10px;
-      display: flex;
-      flex-flow: row;
-      justify-content: space-between;
-      align-items: center;
-      width: 120px;
-      $btn-size: 24px;
-      .q-btn {
-        width: $btn-size;
-        height: $btn-size;
-        min-height: $btn-size;
-        padding: 0;
-      }
-      .cart-item__count-action-count {
-        color: $primary;
-        font-size: 16px;
-      }
-    }
-    .cart-item__price {
-      position: absolute;
-      top: 136px;
-      right: 20px;
-      display: flex;
-      flex-flow: row;
-      align-items: center;
-      justify-content: space-between;
-      gap: 19px;
-      .cart-item__price-base {
-        color: #FF3D3D;
-        font-size: 18px;
-        text-decoration: line-through;
-      }
-      .cart-item__price-final {
-        color: $primary;
-        font-size: 24px;
-      }
-    }
-    .cart-item__products {
-      display: flex;
-      flex-flow: row;
-      flex-wrap: wrap;
-      .cart-item__product {
-        width: 33.33%;
-        margin-bottom: 17px;
-        &:before {
-          display: inline-block;
-          content: ' ';
-          width: 8px;
-          height: 8px;
-          background: #475F4A;
-          border-radius: 100%;
-          margin-right: 4px;
-        }
-      }
-    }
+    text-align: center;
   }
 }
 </style>

+ 88 - 0
src/components/Widgets/Store/Cart/components/step2/cartItems.vue

@@ -0,0 +1,88 @@
+<template>
+  <div class="cart-items">
+    <div class="cart-items__title">
+      <q-icon name="shopping_cart" />
+      خرید شما:
+    </div>
+    <div class="cart-items__items row q-col-gutter-md">
+      <div v-for="(basketItem, basketItemIndex) in basketItems.list"
+           :key="basketItemIndex"
+           class="col-md-2 col-sm-6 col-xs-12">
+        <cart-item :basket-item="basketItem"
+                   class="cart-items__item" />
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import CartItem from './cartItem.vue'
+import { Product } from 'src/models/Product'
+import { BasketItemList } from 'src/models/BasketItem.js'
+
+export default {
+  name: 'CartItems',
+  components: { CartItem },
+  props: {
+    basketItems: {
+      type: BasketItemList,
+      default: new BasketItemList()
+    }
+  },
+  emits: ['increase', 'decrease', 'remove'],
+  data () {
+    return {
+      step: 1
+    }
+  },
+  computed: {
+    packageOrProductObject () {
+      if (this.basketItem.product) {
+        return this.basketItem.product_info
+      }
+      if (this.basketItem.package) {
+        return this.basketItem.package_info
+      }
+
+      return new Product()
+    },
+    usageIcon () {
+      if (this.basketItem.package) {
+        return 'inventory_2'
+      }
+      if (this.packageOrProductObject.is_physical) {
+        return 'local_shipping'
+      }
+      return 'file_download'
+    }
+  },
+  methods: {
+    onIncrease () {
+      this.$emit('increase', this.basketItem)
+    },
+    onDecrease () {
+      this.$emit('decrease', this.basketItem)
+    },
+    onRemove () {
+      this.$emit('remove', this.basketItem)
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.cart-items {
+  border-radius: 8px;
+  background: #F9F9FB;
+  border: 1px solid #DFE1EC;
+  padding: 20px 38px;
+  .cart-items__title {
+    color: #727272;
+    font-size: 16px;
+    margin-bottom: 22px;
+  }
+  .cart-items__items {
+    padding-left: 20px;
+  }
+}
+</style>

+ 90 - 0
src/components/Widgets/Store/Cart/components/step2/deliveryInfo.vue

@@ -0,0 +1,90 @@
+<template>
+  <div class="delivery-info">
+    <div class="delivery-info__title">
+      <q-icon name="local_shipping" />
+      زمان و شیوه ارسال:
+    </div>
+    <div class="delivery-info__info">
+      <div class="delivery-info__selected-type">
+        <div class="delivery-info__selected-type-radio">
+          <q-radio label="پست پیشتاز" />
+        </div>
+        <div class="delivery-info__selected-type-meta">
+          <q-icon name="schedule" />
+          زمان تقریبی سفارش بر اساس محدوده جغرافیایی
+          48-72
+          ساعت
+        </div>
+        <div class="delivery-info__selected-type-meta">
+          <q-icon name="shopping_bag" />
+          هزینه به آدرس و وزن مرصوله متغیر میباشد
+        </div>
+      </div>
+      <div class="delivery-info__price-info">
+        <div class="delivery-info__price-label">
+          <q-icon name="local_shipping" />
+          هزینه ارسال
+        </div>
+        <div class="delivery-info__price-value">
+          {{ basket.delivery_cost.toLocaleString('fa') }}
+          تومان
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import { Basket } from 'src/models/Basket.js'
+
+export default {
+  name: 'DeliveryInfo',
+  props: {
+    basket: {
+      type: Basket,
+      default: new Basket()
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.delivery-info {
+  border-radius: 8px;
+  background: #F9F9FB;
+  border: 1px solid #DFE1EC;
+  padding: 20px 30px 46px 38px;
+  .delivery-info__title {
+    color: #727272;
+    font-size: 16px;
+    margin-bottom: 22px;
+  }
+  .delivery-info__info {
+    display: flex;
+    flex-flow: row;
+    justify-content: space-between;
+    .delivery-info__selected-type {
+      .delivery-info__selected-type-radio {
+        margin-bottom: 16px;
+      }
+      .delivery-info__selected-type-meta {
+        padding-left: 20px;
+        font-size: 12px;
+        color: #727272;
+        margin-bottom: 16px;
+      }
+    }
+    .delivery-info__price-info {
+      .delivery-info__price-label {
+        color: #616161;
+        font-size: 16px;
+        margin-bottom: 18px;
+      }
+      .delivery-info__price-value {
+        color: $primary;
+        font-size: 18px;
+      }
+    }
+  }
+}
+</style>

+ 17 - 50
src/components/Widgets/Store/Cart/components/step2/step2.vue

@@ -1,18 +1,15 @@
 <template>
-  <div class="step1">
+  <div class="step2">
     <q-linear-progress v-if="basket.loading"
                        indeterminate />
     <template v-if="!basket.loading">
-      <div class="step1__cart-items">
-        <cart-item v-for="(basketItem, basketItemIndex) in basket.items_info.list"
-                   :key="basketItemIndex"
-                   :basket-item="basketItem"
-                   class="step1__cart-item"
-                   @remove="onRemove(basketItem)"
-                   @increase="onIncrease(basketItem)"
-                   @decrease="onDecrease(basketItem)" />
+      <div class="step2__cart-info">
+        <cart-items :basket-items="basket.items_info" />
+        <cart-address :basket="basket"
+                      @set-address="onSetAddress" />
+        <delivery-info :basket="basket" />
       </div>
-      <div class="step1__sidebar">
+      <div class="step2__sidebar">
         <sidebar :basket="basket"
                  @complete="onComplete" />
       </div>
@@ -22,13 +19,15 @@
 
 <script>
 import Sidebar from './sidebar.vue'
-import CartItem from './cartItem.vue'
+import CartItems from './cartItems.vue'
+import CartAddress from './cartAddress.vue'
+import DeliveryInfo from './deliveryInfo.vue'
 import { Basket } from 'src/models/Basket.js'
 import { APIGateway } from 'src/api/APIGateway.js'
 
 export default {
-  name: 'step1',
-  components: { CartItem, Sidebar },
+  name: 'step2',
+  components: { CartItems, CartAddress, Sidebar, DeliveryInfo },
   props: {
     type: {
       type: String,
@@ -55,37 +54,8 @@ export default {
           this.basket.loading = false
         })
     },
-    onIncrease (basketItem) {
-      basketItem.loading = true
-      const newCount = basketItem.count + 1
-      APIGateway.basketItem.update(basketItem.id, newCount)
-        .then(() => {
-          this.checkoutReview()
-        })
-        .finally(() => {
-          basketItem.loading = false
-        })
-    },
-    onDecrease (basketItem) {
-      basketItem.loading = true
-      const newCount = basketItem.count - 1
-      APIGateway.basketItem.update(basketItem.id, newCount)
-        .then(() => {
-          this.checkoutReview()
-        })
-        .finally(() => {
-          basketItem.loading = false
-        })
-    },
-    onRemove (basketItem) {
-      basketItem.loading = true
-      APIGateway.basketItem.update(basketItem.id, 0)
-        .then(() => {
-          this.checkoutReview()
-        })
-        .finally(() => {
-          basketItem.loading = false
-        })
+    onSetAddress () {
+      this.checkoutReview()
     },
     onComplete () {
       this.$emit('complete')
@@ -95,21 +65,18 @@ export default {
 </script>
 
 <style scoped lang="scss">
-.step1 {
+.step2 {
   display: flex;
   flex-flow: row;
   gap: 28px;
   $sidebar-width: 312px;
-  .step1__cart-items {
+  .step2__cart-info {
     width: calc( 100% - #{$sidebar-width} );
     display: flex;
     flex-flow: column;
     gap: 24px;
-    .step1__cart-item {
-
-    }
   }
-  .step1__sidebar {
+  .step2__sidebar {
     width: $sidebar-width;
   }
 }

+ 1 - 1
src/components/Widgets/Store/Cart/components/stepper.vue

@@ -51,7 +51,7 @@ export default {
   emits: ['onChangeFilters'],
   data () {
     return {
-      step: 1
+      step: 2
     }
   },
   methods: {

+ 0 - 75
src/components/Widgets/VideoPlayerWidget/OptionPanel.vue

@@ -1,75 +0,0 @@
-<template>
-  <option-panel-tabs v-model:options="localOptions">
-    <template #main-tab>
-      <div class="option-panel-container">
-        <div class="input-container q-pt-md">
-          <div class="outsideLabel">poster</div>
-          <q-input v-model="localOptions.poster"
-                   label="poster" />
-        </div>
-        <div class="input-container q-py-md">
-          <div>نوع سورس ویدیو را انتخاب کنید:</div>
-          <q-radio v-model="localOptions.srcType"
-                   val="hls"
-                   class="q-mr-md"
-                   label="hls" />
-          <q-radio v-model="localOptions.srcType"
-                   val="singleQuality"
-                   class="q-mr-md"
-                   label="تک کیفیت" />
-          <q-radio v-model="localOptions.srcType"
-                   val="multipleQuality"
-                   class="q-mr-md"
-                   label="چند کیفیت" />
-        </div>
-        <div class="input-container">
-          <div v-if="localOptions.srcType && localOptions.srcType === 'hls'">
-            <div class="outsideLabel">hls</div>
-            <q-input v-model="localOptions.url"
-                     label="poster" />
-          </div>
-          <div v-if="localOptions.srcType && localOptions.srcType === 'singleQuality'">
-            <div class="outsideLabel">تک کیفیت</div>
-            <q-input v-model="localOptions.url"
-                     label="تک کیفیت" />
-          </div>
-          <div v-if="localOptions.srcType && localOptions.srcType === 'multipleQuality'">
-            <div class="input-container q-pt-md">
-              <div class="outsideLabel">سورس کیفیت عالی</div>
-              <q-input v-model="localOptions.src[0]" />
-            </div>
-            <div class="input-container q-pt-md">
-              <div class="outsideLabel">سورس کیفیت خوب</div>
-              <q-input v-model="localOptions.src[1]" />
-            </div>
-            <div class="input-container q-pt-md">
-              <div class="outsideLabel">سورس کیفیت متوسط</div>
-              <q-input v-model="localOptions.src[2]" />
-            </div>
-          </div>
-        </div>
-      </div>
-    </template>
-  </option-panel-tabs>
-</template>
-<script>
-import { defineComponent } from 'vue'
-import OptionPanelTabs from 'quasar-ui-q-page-builder/src/components/OptionPanelComponents/OptionPanelTabs.vue'
-import { PageBuilderOptionPanel } from 'src/mixin/Mixins'
-
-export default defineComponent({
-  name: 'OptionPanel',
-  components: { OptionPanelTabs },
-  mixins: [PageBuilderOptionPanel],
-  data() {
-    return {
-      defaultOptions: {
-        src: '',
-        url: '',
-        poster: '',
-        srcType: ''
-      }
-    }
-  }
-})
-</script>

+ 0 - 40
src/components/Widgets/VideoPlayerWidget/VideoPlayerWidget.vue

@@ -1,40 +0,0 @@
-<template>
-  <div class="video-player-widget-container"
-       :style="localOptions.style"
-       :class="localOptions.customClass">
-    <video-player ref="videoPlayer"
-                  :key="playerKey"
-                  :source="localOptions.url"
-                  :poster="localOptions.poster" />
-  </div>
-</template>
-
-<script>
-import { mixinWidget } from 'src/mixin/Mixins.js'
-import VideoPlayer from 'src/components/VideoPlayer.vue'
-
-export default {
-  name: 'VideoPlayerWidget',
-  components: { VideoPlayer },
-  mixins: [mixinWidget],
-  data() {
-    return {
-      defaultOptions: {
-        src: '',
-        url: '',
-        poster: '',
-        srcType: ''
-      },
-      playerKey: Date.now()
-    }
-  }
-}
-</script>
-
-<style lang="scss" scoped>
-.video-player-widget-container {
-  overflow: hidden;
-  border-radius: 24px;
-  padding: 0 !important;
-}
-</style>

+ 18 - 0
src/models/Basket.js

@@ -100,6 +100,24 @@ class Basket extends Model {
       }
     })
   }
+
+  getFullAddressString () {
+    const addressArray = []
+    if (this.province) {
+      addressArray.push(this.province)
+    }
+    if (this.city) {
+      addressArray.push(this.city)
+    }
+    if (this.address) {
+      addressArray.push(this.address)
+    }
+    if (this.postal_code) {
+      addressArray.push(this.postal_code)
+    }
+
+    return addressArray.join(' - ')
+  }
 }
 
 class BasketList extends Collection {