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`. - Keep backend code under `backend/src/Socialize.Api`.
- The solution file is `backend/Socialize.slnx`. - The solution file is `backend/Socialize.slnx`.
- Backend feature code currently follows FastEndpoints module folders under `Modules/<Feature>`. - 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`. - Frontend runtime config must flow through `frontend/src/config.js`.
- If backend contracts change, run `./scripts/update-openapi.sh` when the backend is running. - 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. - 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. - 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. - 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/ ├─ pages/
├─ plugins/ ├─ plugins/
├─ router/ ├─ router/
─ stores/ ─ stores/
└─ views/
``` ```
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 ## API Contract

View File

@@ -15,8 +15,8 @@
- Use the shared Axios API client in `frontend/src/plugins/api.js` for current JavaScript flows. - 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. - Preserve auth refresh behavior in `authStore` and the API plugin.
- Route-level authenticated app screens live under `/app/*`. - Route-level authenticated app screens live under `/app/*`.
- New isolated feature slices should prefer `frontend/src/features/<feature>/`. - Feature-owned views, stores, composables, constants, utilities, and local components belong under `frontend/src/features/<feature>/`.
- Do not move existing views/stores into feature folders unless a task owns that migration. - Keep only cross-cutting app shell, plugins, shared components, and app-wide stores outside feature folders.
## Docs ## Docs

View File

@@ -30,7 +30,7 @@ backend/tests/Socialize.Tests
## Frontend ## 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 ## API Contract

View File

@@ -14,8 +14,7 @@ You are implementing a frontend task in this repository.
## Instructions ## Instructions
- Implement only the requested frontend task. - Implement only the requested frontend task.
- Existing route-level app screens live under `frontend/src/views/app`. - Feature-owned route views, stores, composables, constants, utilities, and local components belong under `frontend/src/features/<feature>`.
- New isolated feature slices should prefer `frontend/src/features/<feature>`.
- Use `frontend/src/config.js` for runtime configuration. - Use `frontend/src/config.js` for runtime configuration.
- Preserve token refresh behavior in `authStore` and `frontend/src/plugins/api.js`. - 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. - 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. - Do not refactor unrelated code.
- Follow existing backend/frontend structure. - Follow existing backend/frontend structure.
- Backend code currently belongs under `backend/src/Socialize.Api/Modules/<Feature>`. - 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`. - If backend contracts change, update OpenAPI using `./scripts/update-openapi.sh`.
- Do not manually duplicate generated API types when generated types exist. - Do not manually duplicate generated API types when generated types exist.

View File

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

View File

@@ -35,10 +35,10 @@
<script async setup> <script async setup>
import { computed, ref, watch } from 'vue'; import { computed, ref, watch } from 'vue';
import { useRoute } from 'vue-router'; 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 { mdiChevronLeft, mdiChevronRight } from '@mdi/js';
import AppBar from '@/views/main/AppBar.vue'; import AppBar from '@/layouts/main/AppBar.vue';
import AppSidebar from '@/views/main/AppSidebar.vue'; import AppSidebar from '@/layouts/main/AppSidebar.vue';
const route = useRoute(); const route = useRoute();
const authStore = useAuthStore(); 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 {onMounted, ref} from "vue";
import {useHead} from "@vueuse/head"; import {useHead} from "@vueuse/head";
import {useAuthStore} from "@/stores/authStore.js"; import {useAuthStore} from "@/features/auth/stores/authStore.js";
import config from "@/config.js"; import config from "@/config.js";
export function useFacebookLogin() { export function useFacebookLogin() {

View File

@@ -104,7 +104,7 @@
<script setup> <script setup>
import { ref } from 'vue'; import { ref } from 'vue';
import { GoogleLogin } from 'vue3-google-login'; 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 { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { mdiEye, mdiEyeOff, mdiGoogle } from '@mdi/js'; import { mdiEye, mdiEyeOff, mdiGoogle } from '@mdi/js';

View File

@@ -1,8 +1,8 @@
import { computed } from 'vue'; import { computed } from 'vue';
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import { useSessionStorage } from '@vueuse/core'; import { useSessionStorage } from '@vueuse/core';
import { useWorkspaceStore } from '@/stores/workspaceStore.js'; import { useWorkspaceStore } from '@/features/workspaces/stores/workspaceStore.js';
import { useContentItemsStore } from '@/stores/contentItemsStore.js'; import { useContentItemsStore } from '@/features/content/stores/contentItemsStore.js';
export const useChannelsStore = defineStore('channels', () => { export const useChannelsStore = defineStore('channels', () => {
const workspaceStore = useWorkspaceStore(); const workspaceStore = useWorkspaceStore();

View File

@@ -2,9 +2,9 @@
import { computed, reactive, ref, watch } from 'vue'; import { computed, reactive, ref, watch } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useWorkspaceStore } from '@/stores/workspaceStore.js'; import { useWorkspaceStore } from '@/features/workspaces/stores/workspaceStore.js';
import { useContentItemsStore } from '@/stores/contentItemsStore.js'; import { useContentItemsStore } from '@/features/content/stores/contentItemsStore.js';
import { useChannelsStore } from '@/stores/channelsStore.js'; import { useChannelsStore } from '@/features/channels/stores/channelsStore.js';
import { import {
mdiClose, mdiClose,
mdiFacebook, mdiFacebook,

View File

@@ -1,7 +1,7 @@
import { computed, ref, watch } from 'vue'; import { computed, ref, watch } from 'vue';
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import { useAuthStore } from '@/stores/authStore.js'; import { useAuthStore } from '@/features/auth/stores/authStore.js';
import { useWorkspaceStore } from '@/stores/workspaceStore.js'; import { useWorkspaceStore } from '@/features/workspaces/stores/workspaceStore.js';
import { useClient } from '@/plugins/api.js'; import { useClient } from '@/plugins/api.js';
export const useClientsStore = defineStore('clients', () => { export const useClientsStore = defineStore('clients', () => {

View File

@@ -3,10 +3,10 @@
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import AppAvatar from '@/components/AppAvatar.vue'; import AppAvatar from '@/components/AppAvatar.vue';
import ImageCropperDialog from '@/components/ImageCropperDialog.vue'; import ImageCropperDialog from '@/components/ImageCropperDialog.vue';
import { useAuthStore } from '@/stores/authStore.js'; import { useAuthStore } from '@/features/auth/stores/authStore.js';
import { useClientsStore } from '@/stores/clientsStore.js'; import { useClientsStore } from '@/features/clients/stores/clientsStore.js';
import { useProjectsStore } from '@/stores/projectsStore.js'; import { useProjectsStore } from '@/features/projects/stores/projectsStore.js';
import { useContentItemsStore } from '@/stores/contentItemsStore.js'; import { useContentItemsStore } from '@/features/content/stores/contentItemsStore.js';
const authStore = useAuthStore(); const authStore = useAuthStore();
const route = useRoute(); const route = useRoute();

View File

@@ -2,9 +2,9 @@
import { reactive, ref } from 'vue'; import { reactive, ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import AppAvatar from '@/components/AppAvatar.vue'; import AppAvatar from '@/components/AppAvatar.vue';
import { useAuthStore } from '@/stores/authStore.js'; import { useAuthStore } from '@/features/auth/stores/authStore.js';
import { useWorkspaceStore } from '@/stores/workspaceStore.js'; import { useWorkspaceStore } from '@/features/workspaces/stores/workspaceStore.js';
import { useClientsStore } from '@/stores/clientsStore.js'; import { useClientsStore } from '@/features/clients/stores/clientsStore.js';
const authStore = useAuthStore(); const authStore = useAuthStore();
const workspaceStore = useWorkspaceStore(); const workspaceStore = useWorkspaceStore();

View File

@@ -1,6 +1,6 @@
import { reactive, ref } from 'vue'; import { reactive, ref } from 'vue';
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import { useWorkspaceStore } from '@/stores/workspaceStore.js'; import { useWorkspaceStore } from '@/features/workspaces/stores/workspaceStore.js';
import { useClient } from '@/plugins/api.js'; import { useClient } from '@/plugins/api.js';
export const useContentItemDetailStore = defineStore('content-item-detail', () => { export const useContentItemDetailStore = defineStore('content-item-detail', () => {

View File

@@ -1,7 +1,7 @@
import { computed, ref, watch } from 'vue'; import { computed, ref, watch } from 'vue';
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import { useAuthStore } from '@/stores/authStore.js'; import { useAuthStore } from '@/features/auth/stores/authStore.js';
import { useWorkspaceStore } from '@/stores/workspaceStore.js'; import { useWorkspaceStore } from '@/features/workspaces/stores/workspaceStore.js';
import { useClient } from '@/plugins/api.js'; import { useClient } from '@/plugins/api.js';
export const useContentItemsStore = defineStore('content-items', () => { export const useContentItemsStore = defineStore('content-items', () => {

View File

@@ -3,12 +3,12 @@
import { useRoute, useRouter } from 'vue-router'; import { useRoute, useRouter } from 'vue-router';
import { useSessionStorage } from '@vueuse/core'; import { useSessionStorage } from '@vueuse/core';
import AppAvatar from '@/components/AppAvatar.vue'; import AppAvatar from '@/components/AppAvatar.vue';
import { useChannelsStore } from '@/stores/channelsStore.js'; import { useChannelsStore } from '@/features/channels/stores/channelsStore.js';
import { useClientsStore } from '@/stores/clientsStore.js'; import { useClientsStore } from '@/features/clients/stores/clientsStore.js';
import { useContentItemDetailStore } from '@/stores/contentItemDetailStore.js'; import { useContentItemDetailStore } from '@/features/content/stores/contentItemDetailStore.js';
import { useContentItemsStore } from '@/stores/contentItemsStore.js'; import { useContentItemsStore } from '@/features/content/stores/contentItemsStore.js';
import { useProjectsStore } from '@/stores/projectsStore.js'; import { useProjectsStore } from '@/features/projects/stores/projectsStore.js';
import { useWorkspaceStore } from '@/stores/workspaceStore.js'; import { useWorkspaceStore } from '@/features/workspaces/stores/workspaceStore.js';
const route = useRoute(); const route = useRoute();
const router = useRouter(); const router = useRouter();

View File

@@ -1,7 +1,7 @@
<script setup> <script setup>
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useAuthStore } from '@/stores/authStore.js'; import { useAuthStore } from '@/features/auth/stores/authStore.js';
import { useContentItemsStore } from '@/stores/contentItemsStore.js'; import { useContentItemsStore } from '@/features/content/stores/contentItemsStore.js';
const { t } = useI18n(); const { t } = useI18n();
const authStore = useAuthStore(); const authStore = useAuthStore();

View File

@@ -1,7 +1,7 @@
import { computed, ref, watch } from 'vue'; import { computed, ref, watch } from 'vue';
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import { useAuthStore } from '@/stores/authStore.js'; import { useAuthStore } from '@/features/auth/stores/authStore.js';
import { useWorkspaceStore } from '@/stores/workspaceStore.js'; import { useWorkspaceStore } from '@/features/workspaces/stores/workspaceStore.js';
import { useClient } from '@/plugins/api.js'; import { useClient } from '@/plugins/api.js';
export const useNotificationsStore = defineStore('notifications', () => { export const useNotificationsStore = defineStore('notifications', () => {

View File

@@ -1,7 +1,7 @@
import { ref, watch } from 'vue'; import { ref, watch } from 'vue';
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import { useAuthStore } from '@/stores/authStore.js'; import { useAuthStore } from '@/features/auth/stores/authStore.js';
import { useWorkspaceStore } from '@/stores/workspaceStore.js'; import { useWorkspaceStore } from '@/features/workspaces/stores/workspaceStore.js';
import { useClient } from '@/plugins/api.js'; import { useClient } from '@/plugins/api.js';
export const useProjectsStore = defineStore('projects', () => { export const useProjectsStore = defineStore('projects', () => {

View File

@@ -1,10 +1,10 @@
<script setup> <script setup>
import { computed } from 'vue'; import { computed } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { useAuthStore } from '@/stores/authStore.js'; import { useAuthStore } from '@/features/auth/stores/authStore.js';
import { useWorkspaceStore } from '@/stores/workspaceStore.js'; import { useWorkspaceStore } from '@/features/workspaces/stores/workspaceStore.js';
import { useProjectsStore } from '@/stores/projectsStore.js'; import { useProjectsStore } from '@/features/projects/stores/projectsStore.js';
import { useContentItemsStore } from '@/stores/contentItemsStore.js'; import { useContentItemsStore } from '@/features/content/stores/contentItemsStore.js';
const authStore = useAuthStore(); const authStore = useAuthStore();
const route = useRoute(); const route = useRoute();

View File

@@ -2,10 +2,10 @@
import { computed, reactive, ref, watch } from 'vue'; import { computed, reactive, ref, watch } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useAuthStore } from '@/stores/authStore.js'; import { useAuthStore } from '@/features/auth/stores/authStore.js';
import { useClientsStore } from '@/stores/clientsStore.js'; import { useClientsStore } from '@/features/clients/stores/clientsStore.js';
import { useWorkspaceStore } from '@/stores/workspaceStore.js'; import { useWorkspaceStore } from '@/features/workspaces/stores/workspaceStore.js';
import { useProjectsStore } from '@/stores/projectsStore.js'; import { useProjectsStore } from '@/features/projects/stores/projectsStore.js';
const route = useRoute(); const route = useRoute();
const authStore = useAuthStore(); const authStore = useAuthStore();

View File

@@ -1,7 +1,7 @@
import { computed } from 'vue'; import { computed } from 'vue';
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import { useContentItemsStore } from '@/stores/contentItemsStore.js'; import { useContentItemsStore } from '@/features/content/stores/contentItemsStore.js';
import { useProjectsStore } from '@/stores/projectsStore.js'; import { useProjectsStore } from '@/features/projects/stores/projectsStore.js';
const stageByStatus = { const stageByStatus = {
Draft: 'Draft', Draft: 'Draft',

View File

@@ -1,6 +1,6 @@
<script setup> <script setup>
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useReviewQueueStore } from '@/stores/reviewQueueStore.js'; import { useReviewQueueStore } from '@/features/reviews/stores/reviewQueueStore.js';
const { t } = useI18n(); const { t } = useI18n();
const reviewQueueStore = useReviewQueueStore(); const reviewQueueStore = useReviewQueueStore();

View File

@@ -1,6 +1,6 @@
<script setup> <script setup>
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useAuthStore } from '@/stores/authStore.js'; import { useAuthStore } from '@/features/auth/stores/authStore.js';
const authStore = useAuthStore(); const authStore = useAuthStore();
const { t } = useI18n(); const { t } = useI18n();

View File

@@ -1,6 +1,6 @@
import {computed, watch} from 'vue' import {computed, watch} from 'vue'
import {defineStore} from 'pinia' 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 {useClient} from "@/plugins/api.js";
import {useSessionStorage} from "@vueuse/core"; import {useSessionStorage} from "@vueuse/core";

View File

@@ -3,7 +3,7 @@
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import AppAvatar from '@/components/AppAvatar.vue'; import AppAvatar from '@/components/AppAvatar.vue';
import ImageCropperDialog from '@/components/ImageCropperDialog.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 userProfileStore = useUserProfileStore();
const { t } = useI18n(); const { t } = useI18n();

View File

@@ -1,6 +1,6 @@
import { computed, ref, watch } from 'vue'; import { computed, ref, watch } from 'vue';
import { defineStore } from 'pinia'; 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 { useClient } from '@/plugins/api.js';
export const useWorkspaceStore = defineStore('workspace', () => { export const useWorkspaceStore = defineStore('workspace', () => {

View File

@@ -2,9 +2,9 @@
import { computed, ref } from 'vue'; import { computed, ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { mdiChevronLeft, mdiChevronRight } from '@mdi/js'; import { mdiChevronLeft, mdiChevronRight } from '@mdi/js';
import { useWorkspaceStore } from '@/stores/workspaceStore.js'; import { useWorkspaceStore } from '@/features/workspaces/stores/workspaceStore.js';
import { useProjectsStore } from '@/stores/projectsStore.js'; import { useProjectsStore } from '@/features/projects/stores/projectsStore.js';
import { useContentItemsStore } from '@/stores/contentItemsStore.js'; import { useContentItemsStore } from '@/features/content/stores/contentItemsStore.js';
const { t, locale } = useI18n(); const { t, locale } = useI18n();
const workspaceStore = useWorkspaceStore(); const workspaceStore = useWorkspaceStore();

View File

@@ -1,8 +1,8 @@
<script setup> <script setup>
import { computed, onMounted, ref, watch } from 'vue'; import { computed, onMounted, ref, watch } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useAuthStore } from '@/stores/authStore.js'; import { useAuthStore } from '@/features/auth/stores/authStore.js';
import { useWorkspaceStore } from '@/stores/workspaceStore.js'; import { useWorkspaceStore } from '@/features/workspaces/stores/workspaceStore.js';
import { useClient } from '@/plugins/api.js'; import { useClient } from '@/plugins/api.js';
const { locale, t } = useI18n(); const { locale, t } = useI18n();

View File

@@ -2,7 +2,7 @@
import { computed, reactive, ref } from 'vue'; import { computed, reactive, ref } from 'vue';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useWorkspaceStore } from '@/stores/workspaceStore.js'; import { useWorkspaceStore } from '@/features/workspaces/stores/workspaceStore.js';
const router = useRouter(); const router = useRouter();
const { t } = useI18n(); const { t } = useI18n();

View File

@@ -1,7 +1,7 @@
<script setup> <script setup>
import { computed, reactive, ref, watch } from 'vue'; import { computed, reactive, ref, watch } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useWorkspaceStore } from '@/stores/workspaceStore.js'; import { useWorkspaceStore } from '@/features/workspaces/stores/workspaceStore.js';
import { import {
mdiAccountGroupOutline, mdiAccountGroupOutline,
mdiCheckCircleOutline, mdiCheckCircleOutline,

View File

@@ -1 +0,0 @@

View File

@@ -2,8 +2,8 @@
import { computed, onBeforeUnmount, onMounted, ref } from 'vue'; import { computed, onBeforeUnmount, onMounted, ref } from 'vue';
import { useRoute, useRouter } from 'vue-router'; import { useRoute, useRouter } from 'vue-router';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useAuthStore } from '@/stores/authStore.js'; import { useAuthStore } from '@/features/auth/stores/authStore.js';
import { useWorkspaceStore } from '@/stores/workspaceStore.js'; import { useWorkspaceStore } from '@/features/workspaces/stores/workspaceStore.js';
import { import {
mdiChevronDown, mdiChevronDown,
mdiCogOutline, mdiCogOutline,

View File

@@ -3,13 +3,13 @@
import { useRoute, useRouter } from 'vue-router'; import { useRoute, useRouter } from 'vue-router';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import AppAvatar from '@/components/AppAvatar.vue'; import AppAvatar from '@/components/AppAvatar.vue';
import { useAuthStore } from '@/stores/authStore.js'; import { useAuthStore } from '@/features/auth/stores/authStore.js';
import { useChannelsStore } from '@/stores/channelsStore.js'; import { useChannelsStore } from '@/features/channels/stores/channelsStore.js';
import { useLanguageStore } from '@/stores/languageStore.js'; import { useLanguageStore } from '@/stores/languageStore.js';
import { useNotificationsStore } from '@/stores/notificationsStore.js'; import { useNotificationsStore } from '@/features/notifications/stores/notificationsStore.js';
import { useContentItemsStore } from '@/stores/contentItemsStore.js'; import { useContentItemsStore } from '@/features/content/stores/contentItemsStore.js';
import { useProjectsStore } from '@/stores/projectsStore.js'; import { useProjectsStore } from '@/features/projects/stores/projectsStore.js';
import { useUserProfileStore } from '@/stores/userProfileStore.js'; import { useUserProfileStore } from '@/features/user-profile/stores/userProfileStore.js';
import { import {
mdiBellOutline, mdiBellOutline,
mdiCalendarMonthOutline, mdiCalendarMonthOutline,

View File

@@ -19,18 +19,18 @@ import {
VTextField, VTextField,
} from 'vuetify/components'; } from 'vuetify/components';
import vueGoogleOauth from 'vue3-google-login'; import vueGoogleOauth from 'vue3-google-login';
import { useAuthStore } from '@/stores/authStore.js'; import { useAuthStore } from '@/features/auth/stores/authStore.js';
import { useUserProfileStore } from '@/stores/userProfileStore.js'; import { useUserProfileStore } from '@/features/user-profile/stores/userProfileStore.js';
import Toast, { POSITION } from 'vue-toastification'; import Toast, { POSITION } from 'vue-toastification';
import 'vue-toastification/dist/index.css'; import 'vue-toastification/dist/index.css';
import './assets/main.css'; import './assets/main.css';
import { useWorkspaceStore } from '@/stores/workspaceStore.js'; import { useWorkspaceStore } from '@/features/workspaces/stores/workspaceStore.js';
import { useReviewQueueStore } from '@/stores/reviewQueueStore.js'; import { useReviewQueueStore } from '@/features/reviews/stores/reviewQueueStore.js';
import { useContentItemsStore } from '@/stores/contentItemsStore.js'; import { useContentItemsStore } from '@/features/content/stores/contentItemsStore.js';
import { useClientsStore } from '@/stores/clientsStore.js'; import { useClientsStore } from '@/features/clients/stores/clientsStore.js';
import { useProjectsStore } from '@/stores/projectsStore.js'; import { useProjectsStore } from '@/features/projects/stores/projectsStore.js';
import { useNotificationsStore } from '@/stores/notificationsStore.js'; import { useNotificationsStore } from '@/features/notifications/stores/notificationsStore.js';
import { useChannelsStore } from '@/stores/channelsStore.js'; import { useChannelsStore } from '@/features/channels/stores/channelsStore.js';
import { i18n } from '@/plugins/i18n.js'; import { i18n } from '@/plugins/i18n.js';
import config from '@/config.js'; import config from '@/config.js';

View File

@@ -1,5 +1,5 @@
import axios from 'axios'; import axios from 'axios';
import { useAuthStore } from '@/stores/authStore.js'; import { useAuthStore } from '@/features/auth/stores/authStore.js';
import config from '@/config.js'; import config from '@/config.js';
export function useClient() { 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'; import { createRouter, createWebHistory } from 'vue-router';
const LoginView = () => import('@/views/auth/LoginView.vue'); const LoginView = () => import('@/features/auth/views/LoginView.vue');
const Landing = () => import('@/views/main/Landing.vue'); const Landing = () => import('@/features/landing/views/Landing.vue');
const RegisterView = () => import('@/views/auth/RegisterView.vue'); const RegisterView = () => import('@/features/auth/views/RegisterView.vue');
const ForgotPasswordView = () => import('@/views/auth/ForgotPasswordView.vue'); const ForgotPasswordView = () => import('@/features/auth/views/ForgotPasswordView.vue');
const ResetPasswordView = () => import('@/views/auth/ResetPasswordView.vue'); const ResetPasswordView = () => import('@/features/auth/views/ResetPasswordView.vue');
const VerifyEmailView = () => import('@/views/auth/VerifyEmailView.vue'); const VerifyEmailView = () => import('@/features/auth/views/VerifyEmailView.vue');
const OverviewView = () => import('@/views/app/OverviewView.vue'); const OverviewView = () => import('@/features/workspaces/views/OverviewView.vue');
const DashboardView = () => import('@/views/app/DashboardView.vue'); const DashboardView = () => import('@/features/workspaces/views/DashboardView.vue');
const ChannelsView = () => import('@/views/app/ChannelsView.vue'); const ChannelsView = () => import('@/features/channels/views/ChannelsView.vue');
const CampaignsView = () => import('@/views/app/ProjectsView.vue'); const CampaignsView = () => import('@/features/projects/views/ProjectsView.vue');
const CampaignDetailView = () => import('@/views/app/ProjectDetailView.vue'); const CampaignDetailView = () => import('@/features/projects/views/ProjectDetailView.vue');
const MediaLibraryView = () => import('@/views/app/MediaLibraryView.vue'); const MediaLibraryView = () => import('@/features/content/views/MediaLibraryView.vue');
const WorkspaceCreateView = () => import('@/views/app/WorkspaceCreateView.vue'); const WorkspaceCreateView = () => import('@/features/workspaces/views/WorkspaceCreateView.vue');
const SettingsLayoutView = () => import('@/views/app/SettingsLayoutView.vue'); const SettingsLayoutView = () => import('@/features/settings/views/SettingsLayoutView.vue');
const UserSettingsView = () => import('@/views/app/UserSettingsView.vue'); const UserSettingsView = () => import('@/features/user-profile/views/UserSettingsView.vue');
const IntegrationsSettingsView = () => import('@/views/app/IntegrationsSettingsView.vue'); const IntegrationsSettingsView = () => import('@/features/settings/views/IntegrationsSettingsView.vue');
const WorkspaceSettingsView = () => import('@/views/app/WorkspaceSettingsView.vue'); const WorkspaceSettingsView = () => import('@/features/workspaces/views/WorkspaceSettingsView.vue');
const ReviewQueueView = () => import('@/views/app/ReviewQueueView.vue'); const ReviewQueueView = () => import('@/features/reviews/views/ReviewQueueView.vue');
const ContentItemsView = () => import('@/views/app/ContentItemsView.vue'); const ContentItemsView = () => import('@/features/content/views/ContentItemsView.vue');
const ContentItemDetailView = () => import('@/views/app/ContentItemDetailView.vue'); const ContentItemDetailView = () => import('@/features/content/views/ContentItemDetailView.vue');
const routes = [ 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}`;
}