diff --git a/.vscode/extensions.json b/.vscode/extensions.json index a31d6742..f527e216 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -6,8 +6,7 @@ "vue.volar", "gruntfuggly.todo-tree", "mhutchie.git-graph", - "vue.vscode-typescript-vue-plugin", - "Vue.volar" + "vue.vscode-typescript-vue-plugin" ], "unwantedRecommendations": ["octref.vetur", "hookyqr.beautify", "dbaeumer.jshint", "ms-vscode.vscode-typescript-tslint-plugin"] } diff --git a/package.json b/package.json index 93d15903..9ba7665a 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,7 @@ "workbox-strategies": "^6.5.4" }, "engines": { - "node": "^18 || ^16 || ^14.19", + "node": "^20 || ^18 || ^16", "npm": ">= 6.13.4", "yarn": ">= 1.21.1" } diff --git a/src/components/Admin/ManageReports.vue b/src/components/Admin/ManageReports.vue new file mode 100644 index 00000000..1a636eca --- /dev/null +++ b/src/components/Admin/ManageReports.vue @@ -0,0 +1,128 @@ + + + + + diff --git a/src/components/Admin/TableEntry.vue b/src/components/Admin/TableEntry.vue index e6a3c449..7f0ac68a 100644 --- a/src/components/Admin/TableEntry.vue +++ b/src/components/Admin/TableEntry.vue @@ -4,8 +4,8 @@ diff --git a/src/components/__tests__/TheComments.spec.js.dep b/src/components/__tests__/TheComments.spec.js.dep index a564dc42..d22cd922 100644 --- a/src/components/__tests__/TheComments.spec.js.dep +++ b/src/components/__tests__/TheComments.spec.js.dep @@ -36,7 +36,6 @@ describe('TheComment Component', () => { } catch (error) { const errorCode = error.code const errorMessage = error.message - console.log(errorCode, errorMessage) } }) diff --git a/src/components/shared/ItemCard.vue b/src/components/shared/ItemCard.vue index 5666b6f2..a2d1876d 100644 --- a/src/components/shared/ItemCard.vue +++ b/src/components/shared/ItemCard.vue @@ -1,5 +1,5 @@ @@ -62,4 +61,28 @@ function goToUrl() { text-decoration: none; color: black; } + +.article-card-item { + min-width: 619px; + width: 100%; + border: 1px solid #e54757; + border-radius: 24px; + box-shadow: 0 0 5px rgba(0, 0, 0, 0.3); + + @media (max-width: 1440px) { + min-width: 590px; + } + + @media (min-width: 1024px) { + min-width: 470px; + } + + @media (max-width: 768px) { + min-width: 361px; + } + + @media (max-width: 425px) { + min-width: 280px; + } +} diff --git a/src/components/shared/TheEntries.vue b/src/components/shared/TheEntries.vue index 6987b34c..96d236a1 100644 --- a/src/components/shared/TheEntries.vue +++ b/src/components/shared/TheEntries.vue @@ -1,8 +1,13 @@ diff --git a/src/pages/AdminPage.vue b/src/pages/admin/AdminIndex.vue similarity index 60% rename from src/pages/AdminPage.vue rename to src/pages/admin/AdminIndex.vue index e9eb2cb9..291ced2b 100644 --- a/src/pages/AdminPage.vue +++ b/src/pages/admin/AdminIndex.vue @@ -32,30 +32,47 @@ - - - - - - - - - - - - - - + + + + - - - + - - - - + + @@ -64,16 +81,14 @@ + + diff --git a/src/pages/admin/FeedbacksIndex.vue b/src/pages/admin/FeedbacksIndex.vue new file mode 100644 index 00000000..859affa3 --- /dev/null +++ b/src/pages/admin/FeedbacksIndex.vue @@ -0,0 +1,10 @@ + + + diff --git a/src/pages/admin/PromptsIndex.vue b/src/pages/admin/PromptsIndex.vue new file mode 100644 index 00000000..eb2d0793 --- /dev/null +++ b/src/pages/admin/PromptsIndex.vue @@ -0,0 +1,13 @@ + + + diff --git a/src/pages/admin/ReportsIndex.vue b/src/pages/admin/ReportsIndex.vue new file mode 100644 index 00000000..2e6110c2 --- /dev/null +++ b/src/pages/admin/ReportsIndex.vue @@ -0,0 +1,9 @@ + + + diff --git a/src/pages/admin/UsersIndex.vue b/src/pages/admin/UsersIndex.vue new file mode 100644 index 00000000..21581f2f --- /dev/null +++ b/src/pages/admin/UsersIndex.vue @@ -0,0 +1,9 @@ + + + diff --git a/src/pages/profile/FeedbackIndex.vue b/src/pages/profile/FeedbackIndex.vue new file mode 100644 index 00000000..d2cd3319 --- /dev/null +++ b/src/pages/profile/FeedbackIndex.vue @@ -0,0 +1,6 @@ + + diff --git a/src/pages/ProfilePage.vue b/src/pages/profile/ProfileIndex.vue similarity index 51% rename from src/pages/ProfilePage.vue rename to src/pages/profile/ProfileIndex.vue index 8e3d0de3..b55562ba 100644 --- a/src/pages/ProfilePage.vue +++ b/src/pages/profile/ProfileIndex.vue @@ -7,32 +7,22 @@ - - - - - + + + + + - - - - - - - - - - - - - - - - - + @@ -50,10 +40,8 @@ import { ref } from 'vue' const userStore = useUserStore() const user = ref(userStore.getUser) -const tab = ref(userStore.getProfileTab) userStore.$subscribe((_mutation, state) => { user.value = state._user - tab.value = state._profileTab }) diff --git a/src/pages/profile/ProfileTab.vue b/src/pages/profile/ProfileTab.vue new file mode 100644 index 00000000..f5d1d9cc --- /dev/null +++ b/src/pages/profile/ProfileTab.vue @@ -0,0 +1,6 @@ + + diff --git a/src/pages/profile/SettingsIndex.vue b/src/pages/profile/SettingsIndex.vue new file mode 100644 index 00000000..a29d968a --- /dev/null +++ b/src/pages/profile/SettingsIndex.vue @@ -0,0 +1,6 @@ + + diff --git a/src/pages/profile/SubscriptionsIndex.vue b/src/pages/profile/SubscriptionsIndex.vue new file mode 100644 index 00000000..9ca50935 --- /dev/null +++ b/src/pages/profile/SubscriptionsIndex.vue @@ -0,0 +1,6 @@ + + diff --git a/src/router/admin.js b/src/router/admin.js new file mode 100644 index 00000000..d899d886 --- /dev/null +++ b/src/router/admin.js @@ -0,0 +1,57 @@ +import { useUserStore } from 'stores' + +export default [ + { + path: '/admin', + name: 'admin', + beforeEnter: (_to, _from, next) => { + const userStore = useUserStore() + if (userStore.isWriterOrAbove) next() + else next('/') + }, + redirect: { path: 'admin/prompts' }, + component: () => import('pages/admin/AdminIndex.vue'), + children: [ + { + path: 'prompts', + component: () => import('pages/admin/PromptsIndex.vue') + }, + { + path: 'users', + component: () => import('pages/admin/UsersIndex.vue'), + beforeEnter: (_to, _from, next) => { + const userStore = useUserStore() + if (userStore.isAdmin) next() + else next('/') + } + }, + { + path: 'feedbacks', + component: () => import('pages/admin/FeedbacksIndex.vue'), + beforeEnter: (_to, _from, next) => { + const userStore = useUserStore() + if (userStore.isEditorOrAbove) next() + else next('/') + } + }, + { + path: 'errors', + component: () => import('pages/admin/ErrorsIndex.vue'), + beforeEnter: (_to, _from, next) => { + const userStore = useUserStore() + if (userStore.isAdmin) next() + else next('/') + } + }, + { + path: 'reports', + component: () => import('pages/admin/ReportsIndex.vue'), + beforeEnter: (_to, _from, next) => { + const userStore = useUserStore() + if (userStore.isEditorOrAbove) next() + else next('/') + } + } + ] + } +] diff --git a/src/router/profile.js b/src/router/profile.js new file mode 100644 index 00000000..8dfdb7db --- /dev/null +++ b/src/router/profile.js @@ -0,0 +1,30 @@ +export default [ + { + path: 'profile', + component: () => import('pages/profile/ProfileIndex.vue'), + name: 'profile', + redirect: { name: 'profile.index' }, + children: [ + { + name: 'profile.index', + path: '', + component: () => import('pages/profile/ProfileTab.vue') + }, + { + name: 'profile.subscriptions', + path: 'subscriptions', + component: () => import('pages/profile/SubscriptionsIndex.vue') + }, + { + name: 'profile.feedback', + path: 'feedback', + component: () => import('pages/profile/FeedbackIndex.vue') + }, + { + name: 'profile.settings', + path: 'settings', + component: () => import('pages/profile/SettingsIndex.vue') + } + ] + } +] diff --git a/src/router/routes.js b/src/router/routes.js index 650a9be2..8e1244a8 100644 --- a/src/router/routes.js +++ b/src/router/routes.js @@ -1,5 +1,5 @@ -import { useUserStore } from 'stores' - +import profile from './profile' +import admin from './admin' const routes = [ { path: '/robots.txt', @@ -56,19 +56,8 @@ const routes = [ path: 'fan/:username', component: () => import('pages/PublicProfilePage.vue') }, - { - path: 'profile', - component: () => import('pages/ProfilePage.vue') - }, - { - path: 'admin', - component: () => import('pages/AdminPage.vue'), - beforeEnter: (_to, _from, next) => { - const userStore = useUserStore() - if (userStore.isWriterOrAbove) next() - else next('/') - } - } + ...profile, + ...admin ] }, { diff --git a/src/stores/comments.js b/src/stores/comments.js index 37b029b2..e84be359 100644 --- a/src/stores/comments.js +++ b/src/stores/comments.js @@ -176,7 +176,8 @@ export const useCommentStore = defineStore('comments', { transaction.update(doc(db, collectionName, documentId, 'comments', commentId), { author: userStore.getUserIpHash, isAnonymous: true, - text: 'Comment Deleted' + text: 'Comment Deleted', + isDeleted: true }) }).finally(() => (this._isLoading = false)) }, @@ -210,6 +211,10 @@ export const useCommentStore = defineStore('comments', { this._isLoading = true await deleteDoc(doc(db, collectionName, documentId, 'comments', commentId)).finally(() => (this._isLoading = false)) + }, + + async resetComments() { + this._comments = undefined } } }) diff --git a/src/stores/entries.js b/src/stores/entries.js index 87b7c37e..0e769c45 100644 --- a/src/stores/entries.js +++ b/src/stores/entries.js @@ -33,7 +33,8 @@ export const useEntryStore = defineStore('entries', { _entries: undefined, _isLoading: false, _unSubscribe: undefined, - _tab: 'post' + _tab: 'post', + entryDialog: {} }), persist: true, diff --git a/src/stores/index.js b/src/stores/index.js index 3fb80486..8ede81be 100644 --- a/src/stores/index.js +++ b/src/stores/index.js @@ -6,6 +6,7 @@ import { useCommentStore } from './comments' import { useEntryStore } from './entries' import { useErrorStore } from './errors' import { useFeedbackStore } from './feedbacks' +import { useReportStore } from './reports' import { useLikeStore } from './likes' import { useNotificationStore } from './notifications' import { usePromptStore } from './prompts' @@ -54,6 +55,7 @@ export { useEntryStore, useErrorStore, useFeedbackStore, + useReportStore, useLikeStore, useNotificationStore, usePromptStore, diff --git a/src/stores/likes.js b/src/stores/likes.js index 5a98eab4..54c25293 100644 --- a/src/stores/likes.js +++ b/src/stores/likes.js @@ -29,8 +29,6 @@ export const useLikeStore = defineStore('likes', { if (this._unSubscribeLike || this._unSubscribeDislike) { this._unSubscribeLike() this._unSubscribeDislike() - this._likes = undefined - this._dislikes = undefined } this._unSubscribeLike = onSnapshot(likesCollection, (likesSnapshot) => { this._likes = likesSnapshot.docs.map((doc) => ({ id: doc.id, ...doc.data() })) @@ -112,6 +110,11 @@ export const useLikeStore = defineStore('likes', { await deleteDoc(doc.ref) }) this._isLoading = false + }, + + async resetLikes() { + this._likes = undefined + this._dislikes = undefined } } }) diff --git a/src/stores/prompts.js b/src/stores/prompts.js index ef3bab9b..7585ce92 100644 --- a/src/stores/prompts.js +++ b/src/stores/prompts.js @@ -17,7 +17,9 @@ export const usePromptStore = defineStore('prompts', { state: () => ({ _isLoading: false, _prompts: undefined, - _tab: 'post' + _tab: 'post', + promptDialog: false, + entryDialog: {} }), persist: true, diff --git a/src/stores/reports.js b/src/stores/reports.js new file mode 100644 index 00000000..766edd6c --- /dev/null +++ b/src/stores/reports.js @@ -0,0 +1,111 @@ +import { collection, deleteDoc, doc, getDoc, onSnapshot, updateDoc, Timestamp, setDoc } from 'firebase/firestore' +import { defineStore } from 'pinia' +import { db } from 'src/firebase' +import { useUserStore } from './user' +import { useCommentStore } from './comments' + +export const useReportStore = defineStore('reports', { + state: () => ({ + _reports: undefined, + _isLoading: false, + _unSubscribe: undefined + }), + + getters: { + getReports: (state) => state._reports, + isLoading: (state) => state._isLoading, + isLoaded: (state) => !!state._reports + }, + + actions: { + async fetchReports() { + this._isLoading = true + const userStore = useUserStore() + if (!userStore.getUsers) { + await userStore.fetchUsers() + } + + if (!this._unSubscribe) + this._unSubscribe = onSnapshot(collection(db, 'reports'), async (querySnapshot) => { + const reports = querySnapshot.docs.map((doc) => ({ id: doc.id, ...doc.data() })) + + for (const report of reports) { + if (!report.isAnonymous) { + report.author = await getDoc(report.author).then((doc) => doc.data()) + } else { + report.author = userStore.getUserById(report.author.id) || report.author.id + } + } + this.$patch({ _reports: reports }) + }) + this._isLoading = false + }, + + async addReports(payload) { + try { + const userStore = useUserStore() + this._isLoading = true + const report = { + ...payload, + author: userStore.isAuthenticated ? userStore.getUserRef : userStore.getUserIpHash, + created: Timestamp.fromDate(new Date()), + id: Date.now() + '-' + (userStore.isAuthenticated ? userStore.getUserRef : userStore.getUserIpHash), + status: 'New', + isAnonymous: !userStore.isAuthenticated + } + const reportRef = doc(db, 'reports', report.id) + await setDoc(reportRef, report) + } catch (error) { + console.error('Error adding report:', error.message) + } finally { + this._isLoading = false + } + }, + + async deleteReport(id) { + try { + this._isLoading = true + const reportRef = doc(db, 'reports', id) + await deleteDoc(reportRef) + } catch (error) { + console.error('Error deleting document:', error.message) + } finally { + this._isLoading = false + } + }, + + async editStatusReport(id) { + const reportRef = doc(db, 'reports', id) + + try { + const reportSnapshot = await getDoc(reportRef) + if (reportSnapshot.exists()) { + const updatedTimestamp = Timestamp.fromDate(new Date()) + await updateDoc(reportRef, { + status: 'Deleted', + updated: updatedTimestamp + }) + } else { + console.error('Document does not exist') + } + } catch (error) { + console.error('Error updating document status:', error.message) + } finally { + this._isLoading = false + } + }, + + async deleteComment(collectionName, documentId, commentId, reportId) { + try { + this._isLoading = true + const commentStore = useCommentStore() + await commentStore.deleteComment(collectionName, documentId, commentId) + await this.editStatusReport(reportId) + } catch (error) { + console.error('Error deleting comment:', error.message) + } finally { + this._isLoading = false + } + } + } +}) diff --git a/src/stores/shares.js b/src/stores/shares.js index a3792d51..2b7114f1 100644 --- a/src/stores/shares.js +++ b/src/stores/shares.js @@ -54,6 +54,10 @@ export const useShareStore = defineStore('shares', { await deleteDoc(doc.ref) }) this._isLoading = false + }, + + async resetShares() { + this._shares = undefined } } }) diff --git a/src/stores/user.js b/src/stores/user.js index b1b2cdef..777ba57c 100644 --- a/src/stores/user.js +++ b/src/stores/user.js @@ -118,7 +118,13 @@ export const useUserStore = defineStore('user', { .then(() => Notify.create({ color: 'positive', message: 'Account created successfully' })) .catch((error) => console.error(error)) }) - .catch((error) => console.error(error)) + .catch((error) => { + if (error.code === 'auth/email-already-in-use') { + Notify.create({ type: 'negative', message: 'Email already exists' }) + } else { + Notify.create({ type: 'negative', message: 'Something went wrong, Please try again' }) + } + }) .finally(() => (this._isLoading = false)) },