refactor: organize frontend by feature
This commit is contained in:
@@ -22,7 +22,7 @@ Before meaningful code changes, read:
|
||||
- Keep backend code under `backend/src/Socialize.Api`.
|
||||
- The solution file is `backend/Socialize.slnx`.
|
||||
- Backend feature code currently follows FastEndpoints module folders under `Modules/<Feature>`.
|
||||
- Frontend feature work should prefer `frontend/src/features/<feature>` for new isolated slices while preserving existing route/store code until a task migrates it.
|
||||
- Frontend feature-owned code belongs under `frontend/src/features/<feature>`.
|
||||
- Frontend runtime config must flow through `frontend/src/config.js`.
|
||||
- If backend contracts change, run `./scripts/update-openapi.sh` when the backend is running.
|
||||
- Dev servers use HTTP and bind to `0.0.0.0` for LAN access.
|
||||
@@ -119,4 +119,4 @@ Contract changes:
|
||||
|
||||
- Existing checked-in env and appsettings files may include legacy sensitive-looking values; do not propagate those values into new docs or templates.
|
||||
- The frontend is still JavaScript, not the TypeScript starter app generated by the bootstrap script. New OpenAPI scaffolding exists, but migrating app code to generated typed API calls should happen by task.
|
||||
- Some existing frontend code still lives under `views/`, `stores/`, and `plugins/`. Move it into feature folders only when a task explicitly owns that migration.
|
||||
- Feature-owned frontend route views and stores now live under `frontend/src/features/*`; keep future feature work there.
|
||||
|
||||
@@ -55,11 +55,10 @@ frontend/src/
|
||||
├─ pages/
|
||||
├─ plugins/
|
||||
├─ router/
|
||||
├─ stores/
|
||||
└─ views/
|
||||
└─ stores/
|
||||
```
|
||||
|
||||
The generated scaffold expects `app/`, `features/`, `pages/`, `router/`, `stores/`, and `api/`. Socialize currently has substantial existing code under `views/`, `stores/`, and `plugins/`. New isolated feature work should prefer `features/<feature>/`; existing screens should be migrated only by explicit task.
|
||||
Feature-owned frontend code lives under `frontend/src/features/<feature>/`. Feature folders may contain route views, stores, composables, constants, utilities, and local components. Cross-cutting shell code remains in `layouts/`, shared UI remains in `components/`, global plugins remain in `plugins/`, and app-wide stores may remain in `stores/`.
|
||||
|
||||
## API Contract
|
||||
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
- Use the shared Axios API client in `frontend/src/plugins/api.js` for current JavaScript flows.
|
||||
- Preserve auth refresh behavior in `authStore` and the API plugin.
|
||||
- Route-level authenticated app screens live under `/app/*`.
|
||||
- New isolated feature slices should prefer `frontend/src/features/<feature>/`.
|
||||
- Do not move existing views/stores into feature folders unless a task owns that migration.
|
||||
- Feature-owned views, stores, composables, constants, utilities, and local components belong under `frontend/src/features/<feature>/`.
|
||||
- Keep only cross-cutting app shell, plugins, shared components, and app-wide stores outside feature folders.
|
||||
|
||||
## Docs
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ backend/tests/Socialize.Tests
|
||||
|
||||
## Frontend
|
||||
|
||||
The frontend remains the existing Vue 3 app. The scaffold directories `frontend/src/api`, `frontend/src/features`, `frontend/src/pages`, `frontend/src/layouts`, and `frontend/src/app` are available for incremental migration.
|
||||
The frontend remains the existing Vue 3 app. Feature-owned route views and stores live under `frontend/src/features/<feature>`, while shared app shell code stays under `frontend/src/layouts`, `frontend/src/components`, `frontend/src/plugins`, and `frontend/src/router`.
|
||||
|
||||
## API Contract
|
||||
|
||||
|
||||
@@ -14,8 +14,7 @@ You are implementing a frontend task in this repository.
|
||||
## Instructions
|
||||
|
||||
- Implement only the requested frontend task.
|
||||
- Existing route-level app screens live under `frontend/src/views/app`.
|
||||
- New isolated feature slices should prefer `frontend/src/features/<feature>`.
|
||||
- Feature-owned route views, stores, composables, constants, utilities, and local components belong under `frontend/src/features/<feature>`.
|
||||
- Use `frontend/src/config.js` for runtime configuration.
|
||||
- Preserve token refresh behavior in `authStore` and `frontend/src/plugins/api.js`.
|
||||
- Use generated API types from `frontend/src/api/schema.d.ts` when the endpoint has been generated.
|
||||
|
||||
@@ -24,7 +24,7 @@ Implement:
|
||||
- Do not refactor unrelated code.
|
||||
- Follow existing backend/frontend structure.
|
||||
- Backend code currently belongs under `backend/src/Socialize.Api/Modules/<Feature>`.
|
||||
- New isolated frontend feature code should prefer `frontend/src/features/<feature>`.
|
||||
- Frontend feature-owned code belongs under `frontend/src/features/<feature>`.
|
||||
- If backend contracts change, update OpenAPI using `./scripts/update-openapi.sh`.
|
||||
- Do not manually duplicate generated API types when generated types exist.
|
||||
|
||||
|
||||
@@ -18,5 +18,7 @@
|
||||
"vue",
|
||||
"tailwindcss"
|
||||
],
|
||||
"rules": {}
|
||||
"rules": {
|
||||
"tailwindcss/no-custom-classname": "off"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,10 +35,10 @@
|
||||
<script async setup>
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useAuthStore } from '@/stores/authStore.js';
|
||||
import { useAuthStore } from '@/features/auth/stores/authStore.js';
|
||||
import { mdiChevronLeft, mdiChevronRight } from '@mdi/js';
|
||||
import AppBar from '@/views/main/AppBar.vue';
|
||||
import AppSidebar from '@/views/main/AppSidebar.vue';
|
||||
import AppBar from '@/layouts/main/AppBar.vue';
|
||||
import AppSidebar from '@/layouts/main/AppSidebar.vue';
|
||||
|
||||
const route = useRoute();
|
||||
const authStore = useAuthStore();
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
export const REACTIONS = {
|
||||
LIKE: 'Like',
|
||||
DISLIKE: 'Dislike',
|
||||
LOVE: 'Love',
|
||||
HAHA: 'Haha',
|
||||
WOW: 'Wow',
|
||||
SAD: 'Sad',
|
||||
ANGRY: 'Angry'
|
||||
};
|
||||
@@ -1 +0,0 @@
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {onMounted, ref} from "vue";
|
||||
import {useHead} from "@vueuse/head";
|
||||
import {useAuthStore} from "@/stores/authStore.js";
|
||||
import {useAuthStore} from "@/features/auth/stores/authStore.js";
|
||||
import config from "@/config.js";
|
||||
|
||||
export function useFacebookLogin() {
|
||||
@@ -104,7 +104,7 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { GoogleLogin } from 'vue3-google-login';
|
||||
import { useAuthStore } from '@/stores/authStore.js';
|
||||
import { useAuthStore } from '@/features/auth/stores/authStore.js';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { mdiEye, mdiEyeOff, mdiGoogle } from '@mdi/js';
|
||||
@@ -1,8 +1,8 @@
|
||||
import { computed } from 'vue';
|
||||
import { defineStore } from 'pinia';
|
||||
import { useSessionStorage } from '@vueuse/core';
|
||||
import { useWorkspaceStore } from '@/stores/workspaceStore.js';
|
||||
import { useContentItemsStore } from '@/stores/contentItemsStore.js';
|
||||
import { useWorkspaceStore } from '@/features/workspaces/stores/workspaceStore.js';
|
||||
import { useContentItemsStore } from '@/features/content/stores/contentItemsStore.js';
|
||||
|
||||
export const useChannelsStore = defineStore('channels', () => {
|
||||
const workspaceStore = useWorkspaceStore();
|
||||
@@ -2,9 +2,9 @@
|
||||
import { computed, reactive, ref, watch } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useWorkspaceStore } from '@/stores/workspaceStore.js';
|
||||
import { useContentItemsStore } from '@/stores/contentItemsStore.js';
|
||||
import { useChannelsStore } from '@/stores/channelsStore.js';
|
||||
import { useWorkspaceStore } from '@/features/workspaces/stores/workspaceStore.js';
|
||||
import { useContentItemsStore } from '@/features/content/stores/contentItemsStore.js';
|
||||
import { useChannelsStore } from '@/features/channels/stores/channelsStore.js';
|
||||
import {
|
||||
mdiClose,
|
||||
mdiFacebook,
|
||||
@@ -1,7 +1,7 @@
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { defineStore } from 'pinia';
|
||||
import { useAuthStore } from '@/stores/authStore.js';
|
||||
import { useWorkspaceStore } from '@/stores/workspaceStore.js';
|
||||
import { useAuthStore } from '@/features/auth/stores/authStore.js';
|
||||
import { useWorkspaceStore } from '@/features/workspaces/stores/workspaceStore.js';
|
||||
import { useClient } from '@/plugins/api.js';
|
||||
|
||||
export const useClientsStore = defineStore('clients', () => {
|
||||
@@ -3,10 +3,10 @@
|
||||
import { useRoute } from 'vue-router';
|
||||
import AppAvatar from '@/components/AppAvatar.vue';
|
||||
import ImageCropperDialog from '@/components/ImageCropperDialog.vue';
|
||||
import { useAuthStore } from '@/stores/authStore.js';
|
||||
import { useClientsStore } from '@/stores/clientsStore.js';
|
||||
import { useProjectsStore } from '@/stores/projectsStore.js';
|
||||
import { useContentItemsStore } from '@/stores/contentItemsStore.js';
|
||||
import { useAuthStore } from '@/features/auth/stores/authStore.js';
|
||||
import { useClientsStore } from '@/features/clients/stores/clientsStore.js';
|
||||
import { useProjectsStore } from '@/features/projects/stores/projectsStore.js';
|
||||
import { useContentItemsStore } from '@/features/content/stores/contentItemsStore.js';
|
||||
|
||||
const authStore = useAuthStore();
|
||||
const route = useRoute();
|
||||
@@ -2,9 +2,9 @@
|
||||
import { reactive, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import AppAvatar from '@/components/AppAvatar.vue';
|
||||
import { useAuthStore } from '@/stores/authStore.js';
|
||||
import { useWorkspaceStore } from '@/stores/workspaceStore.js';
|
||||
import { useClientsStore } from '@/stores/clientsStore.js';
|
||||
import { useAuthStore } from '@/features/auth/stores/authStore.js';
|
||||
import { useWorkspaceStore } from '@/features/workspaces/stores/workspaceStore.js';
|
||||
import { useClientsStore } from '@/features/clients/stores/clientsStore.js';
|
||||
|
||||
const authStore = useAuthStore();
|
||||
const workspaceStore = useWorkspaceStore();
|
||||
@@ -1,6 +1,6 @@
|
||||
import { reactive, ref } from 'vue';
|
||||
import { defineStore } from 'pinia';
|
||||
import { useWorkspaceStore } from '@/stores/workspaceStore.js';
|
||||
import { useWorkspaceStore } from '@/features/workspaces/stores/workspaceStore.js';
|
||||
import { useClient } from '@/plugins/api.js';
|
||||
|
||||
export const useContentItemDetailStore = defineStore('content-item-detail', () => {
|
||||
@@ -1,7 +1,7 @@
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { defineStore } from 'pinia';
|
||||
import { useAuthStore } from '@/stores/authStore.js';
|
||||
import { useWorkspaceStore } from '@/stores/workspaceStore.js';
|
||||
import { useAuthStore } from '@/features/auth/stores/authStore.js';
|
||||
import { useWorkspaceStore } from '@/features/workspaces/stores/workspaceStore.js';
|
||||
import { useClient } from '@/plugins/api.js';
|
||||
|
||||
export const useContentItemsStore = defineStore('content-items', () => {
|
||||
@@ -3,12 +3,12 @@
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { useSessionStorage } from '@vueuse/core';
|
||||
import AppAvatar from '@/components/AppAvatar.vue';
|
||||
import { useChannelsStore } from '@/stores/channelsStore.js';
|
||||
import { useClientsStore } from '@/stores/clientsStore.js';
|
||||
import { useContentItemDetailStore } from '@/stores/contentItemDetailStore.js';
|
||||
import { useContentItemsStore } from '@/stores/contentItemsStore.js';
|
||||
import { useProjectsStore } from '@/stores/projectsStore.js';
|
||||
import { useWorkspaceStore } from '@/stores/workspaceStore.js';
|
||||
import { useChannelsStore } from '@/features/channels/stores/channelsStore.js';
|
||||
import { useClientsStore } from '@/features/clients/stores/clientsStore.js';
|
||||
import { useContentItemDetailStore } from '@/features/content/stores/contentItemDetailStore.js';
|
||||
import { useContentItemsStore } from '@/features/content/stores/contentItemsStore.js';
|
||||
import { useProjectsStore } from '@/features/projects/stores/projectsStore.js';
|
||||
import { useWorkspaceStore } from '@/features/workspaces/stores/workspaceStore.js';
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup>
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useAuthStore } from '@/stores/authStore.js';
|
||||
import { useContentItemsStore } from '@/stores/contentItemsStore.js';
|
||||
import { useAuthStore } from '@/features/auth/stores/authStore.js';
|
||||
import { useContentItemsStore } from '@/features/content/stores/contentItemsStore.js';
|
||||
|
||||
const { t } = useI18n();
|
||||
const authStore = useAuthStore();
|
||||
@@ -1,7 +1,7 @@
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { defineStore } from 'pinia';
|
||||
import { useAuthStore } from '@/stores/authStore.js';
|
||||
import { useWorkspaceStore } from '@/stores/workspaceStore.js';
|
||||
import { useAuthStore } from '@/features/auth/stores/authStore.js';
|
||||
import { useWorkspaceStore } from '@/features/workspaces/stores/workspaceStore.js';
|
||||
import { useClient } from '@/plugins/api.js';
|
||||
|
||||
export const useNotificationsStore = defineStore('notifications', () => {
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ref, watch } from 'vue';
|
||||
import { defineStore } from 'pinia';
|
||||
import { useAuthStore } from '@/stores/authStore.js';
|
||||
import { useWorkspaceStore } from '@/stores/workspaceStore.js';
|
||||
import { useAuthStore } from '@/features/auth/stores/authStore.js';
|
||||
import { useWorkspaceStore } from '@/features/workspaces/stores/workspaceStore.js';
|
||||
import { useClient } from '@/plugins/api.js';
|
||||
|
||||
export const useProjectsStore = defineStore('projects', () => {
|
||||
@@ -1,10 +1,10 @@
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useAuthStore } from '@/stores/authStore.js';
|
||||
import { useWorkspaceStore } from '@/stores/workspaceStore.js';
|
||||
import { useProjectsStore } from '@/stores/projectsStore.js';
|
||||
import { useContentItemsStore } from '@/stores/contentItemsStore.js';
|
||||
import { useAuthStore } from '@/features/auth/stores/authStore.js';
|
||||
import { useWorkspaceStore } from '@/features/workspaces/stores/workspaceStore.js';
|
||||
import { useProjectsStore } from '@/features/projects/stores/projectsStore.js';
|
||||
import { useContentItemsStore } from '@/features/content/stores/contentItemsStore.js';
|
||||
|
||||
const authStore = useAuthStore();
|
||||
const route = useRoute();
|
||||
@@ -2,10 +2,10 @@
|
||||
import { computed, reactive, ref, watch } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useAuthStore } from '@/stores/authStore.js';
|
||||
import { useClientsStore } from '@/stores/clientsStore.js';
|
||||
import { useWorkspaceStore } from '@/stores/workspaceStore.js';
|
||||
import { useProjectsStore } from '@/stores/projectsStore.js';
|
||||
import { useAuthStore } from '@/features/auth/stores/authStore.js';
|
||||
import { useClientsStore } from '@/features/clients/stores/clientsStore.js';
|
||||
import { useWorkspaceStore } from '@/features/workspaces/stores/workspaceStore.js';
|
||||
import { useProjectsStore } from '@/features/projects/stores/projectsStore.js';
|
||||
|
||||
const route = useRoute();
|
||||
const authStore = useAuthStore();
|
||||
@@ -1,7 +1,7 @@
|
||||
import { computed } from 'vue';
|
||||
import { defineStore } from 'pinia';
|
||||
import { useContentItemsStore } from '@/stores/contentItemsStore.js';
|
||||
import { useProjectsStore } from '@/stores/projectsStore.js';
|
||||
import { useContentItemsStore } from '@/features/content/stores/contentItemsStore.js';
|
||||
import { useProjectsStore } from '@/features/projects/stores/projectsStore.js';
|
||||
|
||||
const stageByStatus = {
|
||||
Draft: 'Draft',
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup>
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useReviewQueueStore } from '@/stores/reviewQueueStore.js';
|
||||
import { useReviewQueueStore } from '@/features/reviews/stores/reviewQueueStore.js';
|
||||
|
||||
const { t } = useI18n();
|
||||
const reviewQueueStore = useReviewQueueStore();
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup>
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useAuthStore } from '@/stores/authStore.js';
|
||||
import { useAuthStore } from '@/features/auth/stores/authStore.js';
|
||||
|
||||
const authStore = useAuthStore();
|
||||
const { t } = useI18n();
|
||||
@@ -1,6 +1,6 @@
|
||||
import {computed, watch} from 'vue'
|
||||
import {defineStore} from 'pinia'
|
||||
import {useAuthStore} from "@/stores/authStore.js";
|
||||
import {useAuthStore} from "@/features/auth/stores/authStore.js";
|
||||
import {useClient} from "@/plugins/api.js";
|
||||
import {useSessionStorage} from "@vueuse/core";
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import AppAvatar from '@/components/AppAvatar.vue';
|
||||
import ImageCropperDialog from '@/components/ImageCropperDialog.vue';
|
||||
import { useUserProfileStore } from '@/stores/userProfileStore.js';
|
||||
import { useUserProfileStore } from '@/features/user-profile/stores/userProfileStore.js';
|
||||
|
||||
const userProfileStore = useUserProfileStore();
|
||||
const { t } = useI18n();
|
||||
@@ -1,6 +1,6 @@
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { defineStore } from 'pinia';
|
||||
import { useAuthStore } from '@/stores/authStore.js';
|
||||
import { useAuthStore } from '@/features/auth/stores/authStore.js';
|
||||
import { useClient } from '@/plugins/api.js';
|
||||
|
||||
export const useWorkspaceStore = defineStore('workspace', () => {
|
||||
@@ -2,9 +2,9 @@
|
||||
import { computed, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { mdiChevronLeft, mdiChevronRight } from '@mdi/js';
|
||||
import { useWorkspaceStore } from '@/stores/workspaceStore.js';
|
||||
import { useProjectsStore } from '@/stores/projectsStore.js';
|
||||
import { useContentItemsStore } from '@/stores/contentItemsStore.js';
|
||||
import { useWorkspaceStore } from '@/features/workspaces/stores/workspaceStore.js';
|
||||
import { useProjectsStore } from '@/features/projects/stores/projectsStore.js';
|
||||
import { useContentItemsStore } from '@/features/content/stores/contentItemsStore.js';
|
||||
|
||||
const { t, locale } = useI18n();
|
||||
const workspaceStore = useWorkspaceStore();
|
||||
@@ -1,8 +1,8 @@
|
||||
<script setup>
|
||||
import { computed, onMounted, ref, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useAuthStore } from '@/stores/authStore.js';
|
||||
import { useWorkspaceStore } from '@/stores/workspaceStore.js';
|
||||
import { useAuthStore } from '@/features/auth/stores/authStore.js';
|
||||
import { useWorkspaceStore } from '@/features/workspaces/stores/workspaceStore.js';
|
||||
import { useClient } from '@/plugins/api.js';
|
||||
|
||||
const { locale, t } = useI18n();
|
||||
@@ -2,7 +2,7 @@
|
||||
import { computed, reactive, ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useWorkspaceStore } from '@/stores/workspaceStore.js';
|
||||
import { useWorkspaceStore } from '@/features/workspaces/stores/workspaceStore.js';
|
||||
|
||||
const router = useRouter();
|
||||
const { t } = useI18n();
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup>
|
||||
import { computed, reactive, ref, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useWorkspaceStore } from '@/stores/workspaceStore.js';
|
||||
import { useWorkspaceStore } from '@/features/workspaces/stores/workspaceStore.js';
|
||||
import {
|
||||
mdiAccountGroupOutline,
|
||||
mdiCheckCircleOutline,
|
||||
@@ -1 +0,0 @@
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
import { computed, onBeforeUnmount, onMounted, ref } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useAuthStore } from '@/stores/authStore.js';
|
||||
import { useWorkspaceStore } from '@/stores/workspaceStore.js';
|
||||
import { useAuthStore } from '@/features/auth/stores/authStore.js';
|
||||
import { useWorkspaceStore } from '@/features/workspaces/stores/workspaceStore.js';
|
||||
import {
|
||||
mdiChevronDown,
|
||||
mdiCogOutline,
|
||||
@@ -3,13 +3,13 @@
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import AppAvatar from '@/components/AppAvatar.vue';
|
||||
import { useAuthStore } from '@/stores/authStore.js';
|
||||
import { useChannelsStore } from '@/stores/channelsStore.js';
|
||||
import { useAuthStore } from '@/features/auth/stores/authStore.js';
|
||||
import { useChannelsStore } from '@/features/channels/stores/channelsStore.js';
|
||||
import { useLanguageStore } from '@/stores/languageStore.js';
|
||||
import { useNotificationsStore } from '@/stores/notificationsStore.js';
|
||||
import { useContentItemsStore } from '@/stores/contentItemsStore.js';
|
||||
import { useProjectsStore } from '@/stores/projectsStore.js';
|
||||
import { useUserProfileStore } from '@/stores/userProfileStore.js';
|
||||
import { useNotificationsStore } from '@/features/notifications/stores/notificationsStore.js';
|
||||
import { useContentItemsStore } from '@/features/content/stores/contentItemsStore.js';
|
||||
import { useProjectsStore } from '@/features/projects/stores/projectsStore.js';
|
||||
import { useUserProfileStore } from '@/features/user-profile/stores/userProfileStore.js';
|
||||
import {
|
||||
mdiBellOutline,
|
||||
mdiCalendarMonthOutline,
|
||||
@@ -19,18 +19,18 @@ import {
|
||||
VTextField,
|
||||
} from 'vuetify/components';
|
||||
import vueGoogleOauth from 'vue3-google-login';
|
||||
import { useAuthStore } from '@/stores/authStore.js';
|
||||
import { useUserProfileStore } from '@/stores/userProfileStore.js';
|
||||
import { useAuthStore } from '@/features/auth/stores/authStore.js';
|
||||
import { useUserProfileStore } from '@/features/user-profile/stores/userProfileStore.js';
|
||||
import Toast, { POSITION } from 'vue-toastification';
|
||||
import 'vue-toastification/dist/index.css';
|
||||
import './assets/main.css';
|
||||
import { useWorkspaceStore } from '@/stores/workspaceStore.js';
|
||||
import { useReviewQueueStore } from '@/stores/reviewQueueStore.js';
|
||||
import { useContentItemsStore } from '@/stores/contentItemsStore.js';
|
||||
import { useClientsStore } from '@/stores/clientsStore.js';
|
||||
import { useProjectsStore } from '@/stores/projectsStore.js';
|
||||
import { useNotificationsStore } from '@/stores/notificationsStore.js';
|
||||
import { useChannelsStore } from '@/stores/channelsStore.js';
|
||||
import { useWorkspaceStore } from '@/features/workspaces/stores/workspaceStore.js';
|
||||
import { useReviewQueueStore } from '@/features/reviews/stores/reviewQueueStore.js';
|
||||
import { useContentItemsStore } from '@/features/content/stores/contentItemsStore.js';
|
||||
import { useClientsStore } from '@/features/clients/stores/clientsStore.js';
|
||||
import { useProjectsStore } from '@/features/projects/stores/projectsStore.js';
|
||||
import { useNotificationsStore } from '@/features/notifications/stores/notificationsStore.js';
|
||||
import { useChannelsStore } from '@/features/channels/stores/channelsStore.js';
|
||||
import { i18n } from '@/plugins/i18n.js';
|
||||
import config from '@/config.js';
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import axios from 'axios';
|
||||
import { useAuthStore } from '@/stores/authStore.js';
|
||||
import { useAuthStore } from '@/features/auth/stores/authStore.js';
|
||||
import config from '@/config.js';
|
||||
|
||||
export function useClient() {
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
import { useAuthStore } from '@/stores/authStore.js';
|
||||
import { useAuthStore } from '@/features/auth/stores/authStore.js';
|
||||
import { createRouter, createWebHistory } from 'vue-router';
|
||||
|
||||
const LoginView = () => import('@/views/auth/LoginView.vue');
|
||||
const Landing = () => import('@/views/main/Landing.vue');
|
||||
const RegisterView = () => import('@/views/auth/RegisterView.vue');
|
||||
const ForgotPasswordView = () => import('@/views/auth/ForgotPasswordView.vue');
|
||||
const ResetPasswordView = () => import('@/views/auth/ResetPasswordView.vue');
|
||||
const VerifyEmailView = () => import('@/views/auth/VerifyEmailView.vue');
|
||||
const OverviewView = () => import('@/views/app/OverviewView.vue');
|
||||
const DashboardView = () => import('@/views/app/DashboardView.vue');
|
||||
const ChannelsView = () => import('@/views/app/ChannelsView.vue');
|
||||
const CampaignsView = () => import('@/views/app/ProjectsView.vue');
|
||||
const CampaignDetailView = () => import('@/views/app/ProjectDetailView.vue');
|
||||
const MediaLibraryView = () => import('@/views/app/MediaLibraryView.vue');
|
||||
const WorkspaceCreateView = () => import('@/views/app/WorkspaceCreateView.vue');
|
||||
const SettingsLayoutView = () => import('@/views/app/SettingsLayoutView.vue');
|
||||
const UserSettingsView = () => import('@/views/app/UserSettingsView.vue');
|
||||
const IntegrationsSettingsView = () => import('@/views/app/IntegrationsSettingsView.vue');
|
||||
const WorkspaceSettingsView = () => import('@/views/app/WorkspaceSettingsView.vue');
|
||||
const ReviewQueueView = () => import('@/views/app/ReviewQueueView.vue');
|
||||
const ContentItemsView = () => import('@/views/app/ContentItemsView.vue');
|
||||
const ContentItemDetailView = () => import('@/views/app/ContentItemDetailView.vue');
|
||||
const LoginView = () => import('@/features/auth/views/LoginView.vue');
|
||||
const Landing = () => import('@/features/landing/views/Landing.vue');
|
||||
const RegisterView = () => import('@/features/auth/views/RegisterView.vue');
|
||||
const ForgotPasswordView = () => import('@/features/auth/views/ForgotPasswordView.vue');
|
||||
const ResetPasswordView = () => import('@/features/auth/views/ResetPasswordView.vue');
|
||||
const VerifyEmailView = () => import('@/features/auth/views/VerifyEmailView.vue');
|
||||
const OverviewView = () => import('@/features/workspaces/views/OverviewView.vue');
|
||||
const DashboardView = () => import('@/features/workspaces/views/DashboardView.vue');
|
||||
const ChannelsView = () => import('@/features/channels/views/ChannelsView.vue');
|
||||
const CampaignsView = () => import('@/features/projects/views/ProjectsView.vue');
|
||||
const CampaignDetailView = () => import('@/features/projects/views/ProjectDetailView.vue');
|
||||
const MediaLibraryView = () => import('@/features/content/views/MediaLibraryView.vue');
|
||||
const WorkspaceCreateView = () => import('@/features/workspaces/views/WorkspaceCreateView.vue');
|
||||
const SettingsLayoutView = () => import('@/features/settings/views/SettingsLayoutView.vue');
|
||||
const UserSettingsView = () => import('@/features/user-profile/views/UserSettingsView.vue');
|
||||
const IntegrationsSettingsView = () => import('@/features/settings/views/IntegrationsSettingsView.vue');
|
||||
const WorkspaceSettingsView = () => import('@/features/workspaces/views/WorkspaceSettingsView.vue');
|
||||
const ReviewQueueView = () => import('@/features/reviews/views/ReviewQueueView.vue');
|
||||
const ContentItemsView = () => import('@/features/content/views/ContentItemsView.vue');
|
||||
const ContentItemDetailView = () => import('@/features/content/views/ContentItemDetailView.vue');
|
||||
|
||||
const routes = [
|
||||
{
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
/**
|
||||
* Regular expression for matching YouTube video IDs in various URL formats
|
||||
*/
|
||||
const VIDEO_ID_REGEX = /(?:youtube\.com\/(?:[^\/]+\/.+\/|(?:v|e(?:mbed)?)\/|.*[?&]v=)|youtu\.be\/)([^"&?\/\s]{11})/i;
|
||||
|
||||
/**
|
||||
* Regular expression for matching standalone YouTube video IDs
|
||||
*/
|
||||
const SHORT_URL_REGEX = /^[a-zA-Z0-9_-]{11}$/;
|
||||
|
||||
/**
|
||||
* Extracts the video ID from a YouTube URL or returns the input if it's already a video ID.
|
||||
* @param {string} input - The YouTube URL or video ID
|
||||
* @returns {string|null} The extracted video ID or null if invalid
|
||||
*/
|
||||
export function extractVideoId(input) {
|
||||
if (!input) return null;
|
||||
|
||||
// If it's already a valid video ID, return it
|
||||
if (isValidVideoId(input)) {
|
||||
return input;
|
||||
}
|
||||
|
||||
// Try to extract video ID from URL
|
||||
const match = input.match(VIDEO_ID_REGEX);
|
||||
return match ? match[1] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates if the input is a valid YouTube video ID.
|
||||
* @param {string} input - The video ID to validate
|
||||
* @returns {boolean} True if the input is a valid video ID
|
||||
*/
|
||||
export function isValidVideoId(input) {
|
||||
if (!input) return false;
|
||||
return SHORT_URL_REGEX.test(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates if the input is a valid YouTube URL or video ID.
|
||||
* @param {string} input - The URL or video ID to validate
|
||||
* @returns {boolean} True if the input is a valid YouTube URL or video ID
|
||||
*/
|
||||
export function isValidYouTubeUrlOrId(input) {
|
||||
if (!input) return false;
|
||||
|
||||
// Check if it's a valid video ID
|
||||
if (isValidVideoId(input)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if it's a valid YouTube URL
|
||||
return VIDEO_ID_REGEX.test(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a YouTube embed URL from a video ID.
|
||||
* @param {string} videoId - The YouTube video ID
|
||||
* @returns {string} The YouTube embed URL
|
||||
*/
|
||||
export function buildEmbedUrl(videoId) {
|
||||
if (!videoId) return '';
|
||||
return `https://www.youtube.com/embed/${videoId}`;
|
||||
}
|
||||
Reference in New Issue
Block a user