diff --git a/.forgejo/workflows/make-image.yml b/.forgejo/workflows/make-image.yml
new file mode 100644
index 0000000..9296fa4
--- /dev/null
+++ b/.forgejo/workflows/make-image.yml
@@ -0,0 +1,40 @@
+on:
+ push:
+ tags:
+ - 'v[0-9]+*'
+ workflow_dispatch:
+
+env:
+ IMAGE_NAME: mtv2-frontend
+
+jobs:
+ build-and-push:
+ name: Make image
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v3
+
+ - name: Login to Forgejo
+ uses: docker/login-action@v3
+ with:
+ registry: repo.nqws.ru
+ username: ${{ secrets.MAINTAINER_USERNAME }}
+ password: ${{ secrets.MAINTAINER_TOKEN }}
+
+ - name: Extract version from tag
+ id: extract_version
+ run: |
+ VERSION=${GITHUB_REF#refs/tags/}
+ echo "VERSION=${VERSION}" >> $GITHUB_ENV
+
+ - name: Make image
+ run: |
+ docker buildx build --platform linux/amd64 \
+ --tag repo.nqws.ru/${{ github.repository }}:latest \
+ --tag repo.nqws.ru/${{ github.repository }}:${{ env.VERSION }} \
+ --push .
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
index 6ff35c8..7737ca2 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,5 +1,17 @@
+FROM node:24.10-alpine3.22 AS builder
+
+WORKDIR /app
+
+COPY package*.json ./
+RUN npm ci --prefer-offline --no-audit
+
+COPY . .
+
+RUN npm run build
+
FROM nginx:alpine
-COPY dist/ /usr/share/nginx/html
+
+COPY --from=builder /app/dist/ /usr/share/nginx/html/
COPY nginx.conf /etc/nginx/nginx.conf
diff --git a/src/App.vue b/src/App.vue
index d6e1430..ec2a28a 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -1,23 +1,40 @@
-
+
+
+
+
+
-
-
+
+
-
-
-
+
+
+
-
-
+
+
+
+
+
+
+
diff --git a/src/components/ChartBlock.vue b/src/components/ChartBlock.vue
index c70f039..96a5243 100644
--- a/src/components/ChartBlock.vue
+++ b/src/components/ChartBlock.vue
@@ -137,11 +137,9 @@ const chartOptions = {
watch(
() => props.chartsData,
(newData) => {
- // Сброс состояния
showPlaceholder.value = false
placeholderMessage.value = ''
- // Случай 1: null или undefined
if (newData === null || newData === undefined) {
showPlaceholder.value = true
placeholderMessage.value = 'No prices found'
@@ -149,7 +147,6 @@ watch(
return
}
- // Случай 2: явная ошибка в объекте
if (typeof newData === 'object' && newData.error) {
showPlaceholder.value = true
placeholderMessage.value = newData.error
@@ -157,13 +154,11 @@ watch(
return
}
- // Случай 3: нормальный объект — пробуем преобразовать
let datasets = []
if (newData.origins && Array.isArray(newData.origins)) {
datasets = transformToChartJSSeries(newData)
}
- // 🔥 Ключевое изменение: проверяем, есть ли хоть один датасет
if (datasets.length === 0) {
showPlaceholder.value = true
placeholderMessage.value = 'No prices found'
diff --git a/src/components/CopyToClipboard.vue b/src/components/CopyToClipboard.vue
new file mode 100644
index 0000000..0100e5f
--- /dev/null
+++ b/src/components/CopyToClipboard.vue
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/components/Footer/Footer.vue b/src/components/Footer/Footer.vue
new file mode 100644
index 0000000..9fdc12e
--- /dev/null
+++ b/src/components/Footer/Footer.vue
@@ -0,0 +1,12 @@
+
+
+
+
+ Merch tracker 2024 - 2025
+
+
+
diff --git a/src/main.js b/src/main.js
index aa2ca8c..753d4a0 100644
--- a/src/main.js
+++ b/src/main.js
@@ -7,7 +7,10 @@ import App from './App.vue'
import router from './router'
const app = createApp(App)
-export const BASE_URL = 'http://localhost:9090/api/v2'
+export const BASE_URL = 'https://api.nqws.ru/api/v2'
+// export const BASE_URL = 'http://localhost:9090/api/v2'
+
+export const BASE_MANDARAKE_LINK = 'https://order.mandarake.co.jp/order/listPage/list?soldOut=1&keyword='
app.use(createPinia())
app.use(router)
diff --git a/src/styles/styles.css b/src/styles/styles.css
index b8994a0..5c4660b 100644
--- a/src/styles/styles.css
+++ b/src/styles/styles.css
@@ -2,6 +2,10 @@
margin-top: 10px;
}
+.mb-10 {
+ margin-bottom: 10px;
+}
+
.mb-20 {
margin-bottom: 30px;
}
@@ -53,6 +57,10 @@
text-align: center;
}
+.text-muted {
+ opacity: 0.6;
+}
+
.sticky-search-container {
position: sticky;
top: 0;
@@ -100,3 +108,15 @@
:deep(.mobile-full-width) {
width: 100%;
}
+
+.default-color{
+ color: #18a058;
+}
+
+.underline {
+ text-decoration: underline;
+}
+
+.link-like-text {
+ cursor: pointer;
+}
diff --git a/src/views/AddMerchView.vue b/src/views/AddMerchView.vue
index eb12ff6..a1af5f8 100644
--- a/src/views/AddMerchView.vue
+++ b/src/views/AddMerchView.vue
@@ -2,11 +2,10 @@
import router from '@/router/index.js'
import { computed, ref } from 'vue'
import { useMerchApi } from '@/api/merch.js'
+import { BASE_MANDARAKE_LINK } from '@/main.js'
const { addMerch } = useMerchApi()
-const mandarakeLink = 'https://order.mandarake.co.jp/order/listPage/list?soldOut=1&keyword='
-
const name = ref('')
// surugaya block
@@ -17,7 +16,7 @@ const checkAutoComplete = ref(true)
const customLink = ref('')
const mandarakeAutocomplete = computed(() => {
- return `${mandarakeLink}${name.value}`
+ return `${BASE_MANDARAKE_LINK}${name.value}`
})
const mandarakeResultLink = computed({
diff --git a/src/views/DetailsView.vue b/src/views/DetailsView.vue
index 587d625..1c54897 100644
--- a/src/views/DetailsView.vue
+++ b/src/views/DetailsView.vue
@@ -6,6 +6,7 @@ import PeriodSelector from '@/components/PeriodSelector.vue'
import ChartBlock from '@/components/ChartBlock.vue'
import { useChartsApi } from '@/api/charts.js'
import EditLink from '@/views/DetailsView/EditLink.vue'
+import CopyToClipboard from '@/components/CopyToClipboard.vue'
const { getMerchDetails, deleteMerch } = useMerchApi()
const { getDistinctPrices } = useChartsApi()
@@ -17,6 +18,11 @@ const props = defineProps({
},
})
+const editing = ref({
+ surugaya: false,
+ mandarake: false,
+})
+
const merchDetails = ref(null)
const loading = ref(true)
const error = ref(null)
@@ -76,11 +82,15 @@ const fetchPrices = async (days = 7) => {
}
}
+function handleLinkUpdate(origin, newLink) {
+ merchDetails.value[`origin_${origin}`].link = newLink
+ editing.value[origin] = false
+}
+
function handleSelectDays(days) {
fetchPrices(days)
}
-
onMounted(() => {
fetchMerch()
fetchPrices(7)
@@ -102,20 +112,71 @@ onMounted(() => {
- Surugaya
+
+
+ Surugaya
+
+
+
+ :model-value="merchDetails.origin_surugaya?.link || ''"
+ @update:model-value="handleLinkUpdate('surugaya', $event)"
+ @cancel-edit="editing.surugaya = false"
+ />
+
+
+
+ Mandarake
+
+
- Mandarake
-
+ :model-value="merchDetails.origin_mandarake?.link || ''"
+ @update:model-value="handleLinkUpdate('mandarake', $event)"
+ @cancel-edit="editing.mandarake = false"
+ />
Not found
diff --git a/src/views/DetailsView/EditLink.vue b/src/views/DetailsView/EditLink.vue
index 81a2376..d855a40 100644
--- a/src/views/DetailsView/EditLink.vue
+++ b/src/views/DetailsView/EditLink.vue
@@ -1,33 +1,26 @@
-
-
- {{ displayValue }}
-
-
+
+ Insert auto-completed link
+
+
- ✓
- ✕
+ Save
+ Cancel
+
+ Clear link
+
+
+
+
+ Confirm clear link
+
+
+ Clear
+ Cancel
+
+