diff --git a/package.json b/package.json index 435fb1c..2f00c55 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ }, "dependencies": { "axios": "^1.13.2", + "date-fns": "^4.1.0", "loglevel": "^1.9.2", "lucide-react": "^0.554.0", "music-metadata": "^11.10.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 83284e7..aa2569f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,46 +11,49 @@ importers: axios: specifier: ^1.13.2 version: 1.13.2 + date-fns: + specifier: ^4.1.0 + version: 4.1.0 loglevel: specifier: ^1.9.2 version: 1.9.2 lucide-react: specifier: ^0.554.0 - version: 0.554.0(react@19.2.0) + version: 0.554.0(react@18.3.1) music-metadata: specifier: ^11.10.3 version: 11.10.3 react: - specifier: ^19.1.1 - version: 19.2.0 + specifier: ^18.3.1 + version: 18.3.1 react-dom: - specifier: ^19.1.1 - version: 19.2.0(react@19.2.0) + specifier: ^18.3.1 + version: 18.3.1(react@18.3.1) react-router-dom: specifier: ^7.9.6 - version: 7.9.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + version: 7.9.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) recharts: specifier: ^3.5.0 - version: 3.5.0(@types/react@19.2.2)(eslint@9.39.1)(react-dom@19.2.0(react@19.2.0))(react-is@19.2.0)(react@19.2.0)(redux@5.0.1) + version: 3.5.0(@types/react@18.3.27)(eslint@9.39.1)(react-dom@18.3.1(react@18.3.1))(react-is@19.2.0)(react@18.3.1)(redux@5.0.1) space-react-client: specifier: ^0.3.0 - version: 0.3.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + version: 0.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) wavesurfer.js: specifier: ^7.11.1 version: 7.12.1 zustand: specifier: ^5.0.9 - version: 5.0.9(@types/react@19.2.2)(immer@11.0.0)(react@19.2.0)(use-sync-external-store@1.6.0(react@19.2.0)) + version: 5.0.9(@types/react@18.3.27)(immer@11.0.0)(react@18.3.1)(use-sync-external-store@1.6.0(react@18.3.1)) devDependencies: '@eslint/js': specifier: ^9.36.0 version: 9.39.1 '@types/react': - specifier: ^19.1.16 - version: 19.2.2 + specifier: ^18.3.27 + version: 18.3.27 '@types/react-dom': - specifier: ^19.1.9 - version: 19.2.2(@types/react@19.2.2) + specifier: ^18.3.7 + version: 18.3.7(@types/react@18.3.27) '@vitejs/plugin-react-swc': specifier: ^4.1.0 version: 4.2.1(vite@7.2.2) @@ -536,13 +539,16 @@ packages: '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - '@types/react-dom@19.2.2': - resolution: {integrity: sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw==} + '@types/prop-types@15.7.15': + resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==} + + '@types/react-dom@18.3.7': + resolution: {integrity: sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==} peerDependencies: - '@types/react': ^19.2.0 + '@types/react': ^18.0.0 - '@types/react@19.2.2': - resolution: {integrity: sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==} + '@types/react@18.3.27': + resolution: {integrity: sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==} '@types/use-sync-external-store@0.0.6': resolution: {integrity: sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==} @@ -627,8 +633,8 @@ packages: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} - csstype@3.1.3: - resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} d3-array@3.2.4: resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==} @@ -674,6 +680,9 @@ packages: resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} engines: {node: '>=12'} + date-fns@4.1.0: + resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==} + debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} @@ -928,6 +937,9 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + js-yaml@4.1.0: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true @@ -959,6 +971,10 @@ packages: resolution: {integrity: sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==} engines: {node: '>= 0.6.0'} + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + lucide-react@0.554.0: resolution: {integrity: sha512-St+z29uthEJVx0Is7ellNkgTEhaeSoA42I7JjOCBCrc5X6LYMGSv0P/2uS5HDLTExP5tpiqRD2PyUEOS6s9UXA==} peerDependencies: @@ -1044,10 +1060,10 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} - react-dom@19.2.0: - resolution: {integrity: sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==} + react-dom@18.3.1: + resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} peerDependencies: - react: ^19.2.0 + react: ^18.3.1 react-is@19.2.0: resolution: {integrity: sha512-x3Ax3kNSMIIkyVYhWPyO09bu0uttcAIoecO/um/rKGQ4EltYWVYtyiGkS/3xMynrbVQdS69Jhlv8FXUEZehlzA==} @@ -1081,8 +1097,8 @@ packages: react-dom: optional: true - react@19.2.0: - resolution: {integrity: sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==} + react@18.3.1: + resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} engines: {node: '>=0.10.0'} recharts@3.5.0: @@ -1113,8 +1129,8 @@ packages: engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true - scheduler@0.27.0: - resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + scheduler@0.23.2: + resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} set-cookie-parser@2.7.2: resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==} @@ -1421,7 +1437,7 @@ snapshots: '@humanwhocodes/retry@0.4.3': {} - '@reduxjs/toolkit@2.11.0(react-redux@9.2.0(@types/react@19.2.2)(react@19.2.0)(redux@5.0.1))(react@19.2.0)': + '@reduxjs/toolkit@2.11.0(react-redux@9.2.0(@types/react@18.3.27)(react@18.3.1)(redux@5.0.1))(react@18.3.1)': dependencies: '@standard-schema/spec': 1.0.0 '@standard-schema/utils': 0.3.0 @@ -1430,8 +1446,8 @@ snapshots: redux-thunk: 3.1.0(redux@5.0.1) reselect: 5.1.1 optionalDependencies: - react: 19.2.0 - react-redux: 9.2.0(@types/react@19.2.2)(react@19.2.0)(redux@5.0.1) + react: 18.3.1 + react-redux: 9.2.0(@types/react@18.3.27)(react@18.3.1)(redux@5.0.1) '@rolldown/pluginutils@1.0.0-beta.46': {} @@ -1596,13 +1612,16 @@ snapshots: '@types/json-schema@7.0.15': {} - '@types/react-dom@19.2.2(@types/react@19.2.2)': + '@types/prop-types@15.7.15': {} + + '@types/react-dom@18.3.7(@types/react@18.3.27)': dependencies: - '@types/react': 19.2.2 + '@types/react': 18.3.27 - '@types/react@19.2.2': + '@types/react@18.3.27': dependencies: - csstype: 3.1.3 + '@types/prop-types': 15.7.15 + csstype: 3.2.3 '@types/use-sync-external-store@0.0.6': {} @@ -1686,7 +1705,7 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 - csstype@3.1.3: {} + csstype@3.2.3: {} d3-array@3.2.4: dependencies: @@ -1726,6 +1745,8 @@ snapshots: d3-timer@3.0.1: {} + date-fns@4.1.0: {} + debug@4.4.3: dependencies: ms: 2.1.3 @@ -1999,6 +2020,8 @@ snapshots: isexe@2.0.0: {} + js-tokens@4.0.0: {} + js-yaml@4.1.0: dependencies: argparse: 2.0.1 @@ -2026,9 +2049,13 @@ snapshots: loglevel@1.9.2: {} - lucide-react@0.554.0(react@19.2.0): + loose-envify@1.4.0: dependencies: - react: 19.2.0 + js-tokens: 4.0.0 + + lucide-react@0.554.0(react@18.3.1): + dependencies: + react: 18.3.1 math-intrinsics@1.1.0: {} @@ -2105,54 +2132,57 @@ snapshots: punycode@2.3.1: {} - react-dom@19.2.0(react@19.2.0): + react-dom@18.3.1(react@18.3.1): dependencies: - react: 19.2.0 - scheduler: 0.27.0 + loose-envify: 1.4.0 + react: 18.3.1 + scheduler: 0.23.2 react-is@19.2.0: {} - react-redux@9.2.0(@types/react@19.2.2)(react@19.2.0)(redux@5.0.1): + react-redux@9.2.0(@types/react@18.3.27)(react@18.3.1)(redux@5.0.1): dependencies: '@types/use-sync-external-store': 0.0.6 - react: 19.2.0 - use-sync-external-store: 1.6.0(react@19.2.0) + react: 18.3.1 + use-sync-external-store: 1.6.0(react@18.3.1) optionalDependencies: - '@types/react': 19.2.2 + '@types/react': 18.3.27 redux: 5.0.1 - react-router-dom@7.9.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0): + react-router-dom@7.9.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) - react-router: 7.9.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-router: 7.9.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react-router@7.9.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0): + react-router@7.9.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: cookie: 1.0.2 - react: 19.2.0 + react: 18.3.1 set-cookie-parser: 2.7.2 optionalDependencies: - react-dom: 19.2.0(react@19.2.0) + react-dom: 18.3.1(react@18.3.1) - react@19.2.0: {} + react@18.3.1: + dependencies: + loose-envify: 1.4.0 - recharts@3.5.0(@types/react@19.2.2)(eslint@9.39.1)(react-dom@19.2.0(react@19.2.0))(react-is@19.2.0)(react@19.2.0)(redux@5.0.1): + recharts@3.5.0(@types/react@18.3.27)(eslint@9.39.1)(react-dom@18.3.1(react@18.3.1))(react-is@19.2.0)(react@18.3.1)(redux@5.0.1): dependencies: - '@reduxjs/toolkit': 2.11.0(react-redux@9.2.0(@types/react@19.2.2)(react@19.2.0)(redux@5.0.1))(react@19.2.0) + '@reduxjs/toolkit': 2.11.0(react-redux@9.2.0(@types/react@18.3.27)(react@18.3.1)(redux@5.0.1))(react@18.3.1) clsx: 2.1.1 decimal.js-light: 2.5.1 es-toolkit: 1.42.0 eslint-plugin-react-perf: 3.3.3(eslint@9.39.1) eventemitter3: 5.0.1 immer: 10.2.0 - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) react-is: 19.2.0 - react-redux: 9.2.0(@types/react@19.2.2)(react@19.2.0)(redux@5.0.1) + react-redux: 9.2.0(@types/react@18.3.27)(react@18.3.1)(redux@5.0.1) reselect: 5.1.1 tiny-invariant: 1.3.3 - use-sync-external-store: 1.6.0(react@19.2.0) + use-sync-external-store: 1.6.0(react@18.3.1) victory-vendor: 37.3.6 transitivePeerDependencies: - '@types/react' @@ -2197,7 +2227,9 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.52.5 fsevents: 2.3.3 - scheduler@0.27.0: {} + scheduler@0.23.2: + dependencies: + loose-envify: 1.4.0 set-cookie-parser@2.7.2: {} @@ -2227,11 +2259,11 @@ snapshots: source-map-js@1.2.1: {} - space-react-client@0.3.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0): + space-react-client@0.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: axios: 1.13.2 - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) socket.io-client: 4.8.3 tiny-emitter: 2.1.0 transitivePeerDependencies: @@ -2275,9 +2307,9 @@ snapshots: dependencies: punycode: 2.3.1 - use-sync-external-store@1.6.0(react@19.2.0): + use-sync-external-store@1.6.0(react@18.3.1): dependencies: - react: 19.2.0 + react: 18.3.1 victory-vendor@37.3.6: dependencies: @@ -2321,9 +2353,9 @@ snapshots: yocto-queue@0.1.0: {} - zustand@5.0.9(@types/react@19.2.2)(immer@11.0.0)(react@19.2.0)(use-sync-external-store@1.6.0(react@19.2.0)): + zustand@5.0.9(@types/react@18.3.27)(immer@11.0.0)(react@18.3.1)(use-sync-external-store@1.6.0(react@18.3.1)): optionalDependencies: - '@types/react': 19.2.2 + '@types/react': 18.3.27 immer: 11.0.0 - react: 19.2.0 - use-sync-external-store: 1.6.0(react@19.2.0) + react: 18.3.1 + use-sync-external-store: 1.6.0(react@18.3.1) diff --git a/src/App.jsx b/src/App.jsx index 93281fc..4c2da4e 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -21,6 +21,9 @@ import './styles/App.css'; import DashboardsPage from './pages/app/dashboards/DashboardsPage'; import CreateDashboards from './pages/app/dashboards/CreateDashboards'; import EditDashboard from './pages/app/dashboards/EditDashboard'; +import Friends from './pages/app/social/Friends'; +import FriendRequests from './pages/app/social/FriendRequests'; +import UserSearch from './pages/app/social/UserSearch'; import ViewDashboard from './pages/app/dashboards/ViewDashboard'; import CreatePlaylist from './pages/app/beats-interaction/playlist/CreatePlaylist'; import EditPlaylist from './pages/app/beats-interaction/playlist/EditPlaylist'; @@ -51,7 +54,6 @@ function App() { {/* Public Routes - Accessible to everyone */} }> } /> - } /> } /> } /> @@ -96,6 +98,11 @@ function App() { } /> } /> + {/* Rutas del microservicio Social */} + } /> + } /> + } /> + {/* Catch all - redirect to landing */} diff --git a/src/components/features/player/BeatDetailPlayer.jsx b/src/components/features/player/BeatDetailPlayer.jsx index 75e10da..5ad2a3d 100644 --- a/src/components/features/player/BeatDetailPlayer.jsx +++ b/src/components/features/player/BeatDetailPlayer.jsx @@ -8,6 +8,7 @@ import { incrementPlayCount, downloadBeat, getAudioStreamUrl } from '../../../se import LivingWaveform from './LivingWaveform'; import { usePlayerStore } from '../../../store/usePlayerStore'; import './BeatDetailPlayer.css'; +import { Feature, On, Default} from 'space-react-client'; const BeatDetailPlayer = ({ beat, isOwner }) => { // Global player store - SINGLE SOURCE OF TRUTH @@ -287,9 +288,14 @@ const BeatDetailPlayer = ({ beat, isOwner }) => {
{beat.isDownloadable && ( - + + + + + + )}
diff --git a/src/components/feed/FeedItem.css b/src/components/feed/FeedItem.css new file mode 100644 index 0000000..250bde8 --- /dev/null +++ b/src/components/feed/FeedItem.css @@ -0,0 +1,143 @@ +.feed-item-wrapper { + background: rgba(255, 255, 255, 0.05); + border-radius: 12px; + padding: 24px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + transition: box-shadow 0.2s ease; + width: 100%; +} + +.feed-item-wrapper:hover { + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15); +} + +.feed-item { + display: flex; + gap: 20px; + align-items: center; + justify-content: center; + width: 100%; +} + +.feed-icon { + font-size: 2rem; + flex-shrink: 0; + width: 40px; + height: 40px; + display: flex; + align-items: center; + justify-content: center; + background: #f5f5f5; + border-radius: 50%; +} + +.feed-content { + flex: 1; + text-align: center; +} + +.feed-content p { + margin: 0 0 8px 0; + line-height: 1.5; +} + +.feed-content p:last-child { + margin-bottom: 0; +} + +.feed-thumbnail { + width: 100%; + max-width: 400px; + border-radius: 8px; + margin: 12px 0; + display: block; +} + +.feed-link { + color: #FF4500; + text-decoration: none; + font-weight: 600; + transition: color 0.2s ease; +} + +.feed-link:hover { + color: #CC3700; + text-decoration: underline; +} + +.feed-user-link { + color: #7B68EE; + text-decoration: none; + font-weight: 600; + transition: color 0.2s ease; +} + +.feed-user-link:hover { + color: #6A5ACD; + text-decoration: underline; +} + +.feed-friendship-text { + font-size: 1.25rem; + line-height: 1.6; +} + +.feed-connector { + color: #666; + font-weight: 400; + margin: 0 4px; +} + +.feed-action-text { + color: #666; + font-weight: 400; + margin-left: 4px; +} + +.feed-artist { + color: #666; + font-size: 0.9rem; +} + +.feed-quote { + background: #f5f5f5; + border-left: 4px solid #FF4500; + padding: 12px 16px; + margin: 12px 0; + border-radius: 4px; + font-style: italic; + color: #333; +} + +.feed-score { + color: #FF4500; + font-weight: 600; +} + +.feed-item-time { + color: #999; + font-size: 0.875rem; + margin-top: 12px; + display: block; +} + +/* Responsive */ +@media (max-width: 640px) { + .feed-item-wrapper { + padding: 16px; + } + + .feed-item { + gap: 12px; + } + + .feed-icon { + font-size: 1.5rem; + width: 32px; + height: 32px; + } + + .feed-thumbnail { + max-width: 100%; + } +} diff --git a/src/components/feed/FeedItem.jsx b/src/components/feed/FeedItem.jsx new file mode 100644 index 0000000..684e6b6 --- /dev/null +++ b/src/components/feed/FeedItem.jsx @@ -0,0 +1,142 @@ +import { useState, useEffect } from 'react'; +import { Link } from 'react-router-dom'; +import { formatDistanceToNow } from 'date-fns'; +import { es } from 'date-fns/locale'; +import { client } from '@/api/axiosClient'; +import './FeedItem.css'; + +/** + * Enriquece los datos de un usuario con información del perfil completo + */ +async function enrichWithProfile(userId) { + if (!userId) return null; + try { + const response = await client.get(`/profile/${userId}`); + const profile = response.data; + return { + id: userId, + username: profile.username || 'Usuario', + full_name: profile.full_name || null, + avatar: profile.avatar || null, + }; + } catch (error) { + console.warn(`No se pudo cargar perfil ${userId}:`, error.message); + return { id: userId, username: 'Usuario' }; + } +} + +export default function FeedItem({ item }) { + const [usernames, setUsernames] = useState({}); + + // Cargar nombres de usuario para eventos de amistad + useEffect(() => { + if (item.type === 'friendship') { + const loadUsernames = async () => { + try { + const [profileA, profileB] = await Promise.all([ + enrichWithProfile(item.actorId), + enrichWithProfile(item.friendId) + ]); + + setUsernames({ + userA: profileA?.username || 'Usuario', + userB: profileB?.username || 'Usuario' + }); + } catch (err) { + console.error('Error loading usernames:', err); + } + }; + + loadUsernames(); + } + }, [item.type, item.actorId, item.friendId]); + + const renderContent = () => { + switch (item.type) { + case 'friendship': + return ( +
+
🤝
+
+

+ + {usernames.userA || 'Cargando...'} + + y + + {usernames.userB || 'Cargando...'} + + son amigos ahora +

+
+
+ ); + + case 'beat': + return ( +
+
🎵
+
+ {item.thumbnailUrl && ( + {item.title} + )} +

+ Nuevo beat:{' '} + + {item.title} + +

+ {item.metadata?.artist &&

Por {item.metadata.artist}

} +
+
+ ); + + case 'comment': + return ( +
+
💬
+
+

+ {item.metadata?.actorUsername} comentó en{' '} + + {item.metadata?.beatTitle} + +

+
{item.text}
+
+
+ ); + + case 'rating': + return ( +
+
⭐
+
+

+ {item.metadata?.actorUsername} valoró{' '} + + {item.metadata?.beatTitle} + + {' '}con {item.score} ⭐ +

+
+
+ ); + + default: + return null; + } + }; + + return ( +
+ {renderContent()} + + {formatDistanceToNow(new Date(item.createdAt), { + addSuffix: true, + locale: es + })} + +
+ ); +} diff --git a/src/components/forms/BeatForm.jsx b/src/components/forms/BeatForm.jsx index cbf4d93..1149cb1 100644 --- a/src/components/forms/BeatForm.jsx +++ b/src/components/forms/BeatForm.jsx @@ -22,6 +22,8 @@ import Textarea from '../ui/Textarea'; import Toggle from '../ui/Toggle'; import FileUploader from '../ui/FileUploader'; import './BeatForm.css'; +import { useNavigate } from "react-router-dom"; +import { Feature, On, Default, Loading, ErrorFallback } from 'space-react-client'; const BeatForm = ({ initialData = null, @@ -40,6 +42,7 @@ const BeatForm = ({ }); const [audioFile, setAudioFile] = useState(null); const [coverFile, setCoverFile] = useState(null); + const navigate = useNavigate(); // Helper to resolve cover URL safely const getCoverUrl = (data) => { @@ -60,7 +63,6 @@ const BeatForm = ({ // Sync state with initialData if it changes (async load) useEffect(() => { if (initialData) { - console.log('BeatForm received initialData:', initialData); setFormData({ title: initialData.title || '', genre: initialData.genre || '', @@ -71,7 +73,6 @@ const BeatForm = ({ }); const derivedCoverUrl = getCoverUrl(initialData); - console.log('Derived Cover URL:', derivedCoverUrl); setCoverPreviewUrl(derivedCoverUrl); } }, [initialData]); @@ -123,22 +124,21 @@ const BeatForm = ({ const fileExtension = file.name.split('.').pop().toLowerCase(); if (!validTypes.includes(file.type) && !validExtensions.includes(fileExtension)) { - setError('Invalid file type. Allowed: MP3, WAV, FLAC, AAC'); + setError('Tipo de archivo inválido. Permitidos: MP3, WAV, FLAC, AAC'); return false; } if (file.size > 50 * 1024 * 1024) { - setError('File size too large. Maximum size is 50MB.'); + setError('Archivo demasiado grande. El tamaño máximo es 50MB.'); return false; } try { const metadata = await parseBlob(file); if (!metadata.format.codec) { - setError('Invalid audio file content.'); + setError('Contenido de archivo de audio inválido.'); return false; } } catch (err) { - console.error('Error parsing audio file:', err); - setError('Failed to verify audio file.'); + setError('Error al verificar el archivo de audio.'); return false; } @@ -177,7 +177,7 @@ const BeatForm = ({ const handleSubmit = (e) => { e.preventDefault(); - if (!isEditing && !audioFile) { setError('Please upload an audio file.'); return; } + if (!isEditing && !audioFile) { setError('Por favor, sube un archivo de audio.'); return; } const submitData = { ...formData, }; @@ -204,8 +204,8 @@ const BeatForm = ({
-

Audio File

-

Upload your beat (MP3, WAV, FLAC, AAC)

+

Archivo de Audio

+

Sube tu beat (MP3, WAV, FLAC, AAC)

@@ -216,42 +216,64 @@ const BeatForm = ({ file={audioFile} onChange={handleAudioChange} onClear={handleClearAudio} - title="Drop your beat here" - description="or click to browse files" + title="Arrastra tu beat aquí" + description="o haz clic para buscar archivos" formats="MP3, WAV, FLAC, AAC" - maxSizeText="Max 50MB" + maxSizeText="Máx 50MB" /> )} {/* Cover Art Section */} - -
- -
-

Cover Art

-

Upload an image for your beat

-
-
+ + + +
+ +
+

Portada

+

Sube una imagen para tu beat

+
+
-
- -
-
+
+ +
+
+ + + + + + Comprobando tu plan... + + + Error al verificar tu plan + + {/* Right Column: Metadata */} @@ -261,27 +283,27 @@ const BeatForm = ({
-

Beat Information

-

Add details about your beat

+

Información del Beat

+

Añade detalles sobre tu beat

{/* Title - Full Width Directo */} } name="title" value={formData.title} onChange={handleInputChange} - placeholder="Enter beat title" + placeholder="Introduce el título del beat" fullWidth required /> {/* Genre */}