refactor: organize frontend by feature
Some checks failed
Backend CI/CD / build_and_deploy (push) Has been cancelled
Frontend CI/CD / build_and_deploy (push) Has been cancelled

This commit is contained in:
2026-04-25 01:05:50 -04:00
parent b6eb692c27
commit 121757546a
60 changed files with 107 additions and 183 deletions

View File

@@ -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.

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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.

View File

@@ -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.

View File

@@ -18,5 +18,7 @@
"vue",
"tailwindcss"
],
"rules": {}
"rules": {
"tailwindcss/no-custom-classname": "off"
}
}

View File

@@ -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();

View File

@@ -1 +0,0 @@

View File

@@ -1,9 +0,0 @@
export const REACTIONS = {
LIKE: 'Like',
DISLIKE: 'Dislike',
LOVE: 'Love',
HAHA: 'Haha',
WOW: 'Wow',
SAD: 'Sad',
ANGRY: 'Angry'
};

View File

@@ -1 +0,0 @@

View File

@@ -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() {

View File

@@ -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';

View File

@@ -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();

View File

@@ -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,

View File

@@ -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', () => {

View File

@@ -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();

View File

@@ -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();

View File

@@ -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', () => {

View File

@@ -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', () => {

View File

@@ -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();

View File

@@ -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();

View File

@@ -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', () => {

View File

@@ -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', () => {

View File

@@ -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();

View File

@@ -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();

View File

@@ -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',

View File

@@ -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();

View File

@@ -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();

View File

@@ -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";

View File

@@ -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();

View File

@@ -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', () => {

View File

@@ -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();

View File

@@ -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();

View File

@@ -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();

View File

@@ -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,

View File

@@ -1 +0,0 @@

View File

@@ -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,

View File

@@ -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,

View File

@@ -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';

View File

@@ -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() {

View File

@@ -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 = [
{

View File

@@ -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}`;
}