diff --git a/package-lock.json b/package-lock.json index 909dcdc..2301919 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,8 +5,11 @@ "requires": true, "packages": { "": { + "name": "singsalot-ai", "version": "0.0.0", "dependencies": { + "@ionic/core": "^8.6.5", + "@ionic/react": "^8.6.5", "@reduxjs/toolkit": "^2.8.2", "@tailwindcss/postcss": "^4.1.11", "@types/react-router-dom": "^5.3.3", @@ -1221,6 +1224,32 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@ionic/core": { + "version": "8.6.5", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.6.5.tgz", + "integrity": "sha512-HN+6/Q67fEEpRA86QzXSrCahuHwaTPBsa910RuvY0pIYuoY4rpzGPU9ZOQ5q2wBsrln921rroEPU1xdpPKIH8Q==", + "license": "MIT", + "dependencies": { + "@stencil/core": "4.33.1", + "ionicons": "^7.2.2", + "tslib": "^2.1.0" + } + }, + "node_modules/@ionic/react": { + "version": "8.6.5", + "resolved": "https://registry.npmjs.org/@ionic/react/-/react-8.6.5.tgz", + "integrity": "sha512-reUKqlU3cIJoHuDibB8WUd32a7nqg5aMsIfPnXOVydIUsJdvQnwjACbYiP0g+4AFzTVPsw/Cmyqh85GhXGw4WA==", + "license": "MIT", + "dependencies": { + "@ionic/core": "8.6.5", + "ionicons": "^7.0.0", + "tslib": "*" + }, + "peerDependencies": { + "react": ">=16.8.6", + "react-dom": ">=16.8.6" + } + }, "node_modules/@isaacs/fs-minipass": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", @@ -1653,6 +1682,133 @@ "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz", "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==" }, + "node_modules/@stencil/core": { + "version": "4.33.1", + "resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.33.1.tgz", + "integrity": "sha512-12k9xhAJBkpg598it+NRmaYIdEe6TSnsL/v6/KRXDcUyTK11VYwZQej2eHnMWtqot+znJ+GNTqb5YbiXi+5Low==", + "license": "MIT", + "bin": { + "stencil": "bin/stencil" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.10.0" + }, + "optionalDependencies": { + "@rollup/rollup-darwin-arm64": "4.34.9", + "@rollup/rollup-darwin-x64": "4.34.9", + "@rollup/rollup-linux-arm64-gnu": "4.34.9", + "@rollup/rollup-linux-arm64-musl": "4.34.9", + "@rollup/rollup-linux-x64-gnu": "4.34.9", + "@rollup/rollup-linux-x64-musl": "4.34.9", + "@rollup/rollup-win32-arm64-msvc": "4.34.9", + "@rollup/rollup-win32-x64-msvc": "4.34.9" + } + }, + "node_modules/@stencil/core/node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.9.tgz", + "integrity": "sha512-0CY3/K54slrzLDjOA7TOjN1NuLKERBgk9nY5V34mhmuu673YNb+7ghaDUs6N0ujXR7fz5XaS5Aa6d2TNxZd0OQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@stencil/core/node_modules/@rollup/rollup-darwin-x64": { + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.9.tgz", + "integrity": "sha512-eOojSEAi/acnsJVYRxnMkPFqcxSMFfrw7r2iD9Q32SGkb/Q9FpUY1UlAu1DH9T7j++gZ0lHjnm4OyH2vCI7l7Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@stencil/core/node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.9.tgz", + "integrity": "sha512-6TZjPHjKZUQKmVKMUowF3ewHxctrRR09eYyvT5eFv8w/fXarEra83A2mHTVJLA5xU91aCNOUnM+DWFMSbQ0Nxw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@stencil/core/node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.9.tgz", + "integrity": "sha512-LD2fytxZJZ6xzOKnMbIpgzFOuIKlxVOpiMAXawsAZ2mHBPEYOnLRK5TTEsID6z4eM23DuO88X0Tq1mErHMVq0A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@stencil/core/node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.9.tgz", + "integrity": "sha512-FwBHNSOjUTQLP4MG7y6rR6qbGw4MFeQnIBrMe161QGaQoBQLqSUEKlHIiVgF3g/mb3lxlxzJOpIBhaP+C+KP2A==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@stencil/core/node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.9.tgz", + "integrity": "sha512-cYRpV4650z2I3/s6+5/LONkjIz8MBeqrk+vPXV10ORBnshpn8S32bPqQ2Utv39jCiDcO2eJTuSlPXpnvmaIgRA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@stencil/core/node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.9.tgz", + "integrity": "sha512-z4mQK9dAN6byRA/vsSgQiPeuO63wdiDxZ9yg9iyX2QTzKuQM7T4xlBoeUP/J8uiFkqxkcWndWi+W7bXdPbt27Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@stencil/core/node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.9.tgz", + "integrity": "sha512-AyleYRPU7+rgkMWbEh71fQlrzRfeP6SyMnRf9XX4fCdDPAJumdSBqYEcWPMzVQ4ScAl7E4oFfK0GUVn77xSwbw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@swc/core": { "version": "1.12.14", "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.12.14.tgz", @@ -3285,6 +3441,15 @@ "node": ">=0.8.19" } }, + "node_modules/ionicons": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/ionicons/-/ionicons-7.4.0.tgz", + "integrity": "sha512-ZK94MMqgzMCPPMhmk8Ouu6goyVHFIlw/ACP6oe3FrikcI0N7CX0xcwVaEbUc0G/v3W0shI93vo+9ve/KpvcNhQ==", + "license": "MIT", + "dependencies": { + "@stencil/core": "^4.0.3" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -5336,6 +5501,26 @@ "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", "dev": true }, + "@ionic/core": { + "version": "8.6.5", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.6.5.tgz", + "integrity": "sha512-HN+6/Q67fEEpRA86QzXSrCahuHwaTPBsa910RuvY0pIYuoY4rpzGPU9ZOQ5q2wBsrln921rroEPU1xdpPKIH8Q==", + "requires": { + "@stencil/core": "4.33.1", + "ionicons": "^7.2.2", + "tslib": "^2.1.0" + } + }, + "@ionic/react": { + "version": "8.6.5", + "resolved": "https://registry.npmjs.org/@ionic/react/-/react-8.6.5.tgz", + "integrity": "sha512-reUKqlU3cIJoHuDibB8WUd32a7nqg5aMsIfPnXOVydIUsJdvQnwjACbYiP0g+4AFzTVPsw/Cmyqh85GhXGw4WA==", + "requires": { + "@ionic/core": "8.6.5", + "ionicons": "^7.0.0", + "tslib": "*" + } + }, "@isaacs/fs-minipass": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", @@ -5621,6 +5806,71 @@ "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz", "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==" }, + "@stencil/core": { + "version": "4.33.1", + "resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.33.1.tgz", + "integrity": "sha512-12k9xhAJBkpg598it+NRmaYIdEe6TSnsL/v6/KRXDcUyTK11VYwZQej2eHnMWtqot+znJ+GNTqb5YbiXi+5Low==", + "requires": { + "@rollup/rollup-darwin-arm64": "4.34.9", + "@rollup/rollup-darwin-x64": "4.34.9", + "@rollup/rollup-linux-arm64-gnu": "4.34.9", + "@rollup/rollup-linux-arm64-musl": "4.34.9", + "@rollup/rollup-linux-x64-gnu": "4.34.9", + "@rollup/rollup-linux-x64-musl": "4.34.9", + "@rollup/rollup-win32-arm64-msvc": "4.34.9", + "@rollup/rollup-win32-x64-msvc": "4.34.9" + }, + "dependencies": { + "@rollup/rollup-darwin-arm64": { + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.9.tgz", + "integrity": "sha512-0CY3/K54slrzLDjOA7TOjN1NuLKERBgk9nY5V34mhmuu673YNb+7ghaDUs6N0ujXR7fz5XaS5Aa6d2TNxZd0OQ==", + "optional": true + }, + "@rollup/rollup-darwin-x64": { + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.9.tgz", + "integrity": "sha512-eOojSEAi/acnsJVYRxnMkPFqcxSMFfrw7r2iD9Q32SGkb/Q9FpUY1UlAu1DH9T7j++gZ0lHjnm4OyH2vCI7l7Q==", + "optional": true + }, + "@rollup/rollup-linux-arm64-gnu": { + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.9.tgz", + "integrity": "sha512-6TZjPHjKZUQKmVKMUowF3ewHxctrRR09eYyvT5eFv8w/fXarEra83A2mHTVJLA5xU91aCNOUnM+DWFMSbQ0Nxw==", + "optional": true + }, + "@rollup/rollup-linux-arm64-musl": { + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.9.tgz", + "integrity": "sha512-LD2fytxZJZ6xzOKnMbIpgzFOuIKlxVOpiMAXawsAZ2mHBPEYOnLRK5TTEsID6z4eM23DuO88X0Tq1mErHMVq0A==", + "optional": true + }, + "@rollup/rollup-linux-x64-gnu": { + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.9.tgz", + "integrity": "sha512-FwBHNSOjUTQLP4MG7y6rR6qbGw4MFeQnIBrMe161QGaQoBQLqSUEKlHIiVgF3g/mb3lxlxzJOpIBhaP+C+KP2A==", + "optional": true + }, + "@rollup/rollup-linux-x64-musl": { + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.9.tgz", + "integrity": "sha512-cYRpV4650z2I3/s6+5/LONkjIz8MBeqrk+vPXV10ORBnshpn8S32bPqQ2Utv39jCiDcO2eJTuSlPXpnvmaIgRA==", + "optional": true + }, + "@rollup/rollup-win32-arm64-msvc": { + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.9.tgz", + "integrity": "sha512-z4mQK9dAN6byRA/vsSgQiPeuO63wdiDxZ9yg9iyX2QTzKuQM7T4xlBoeUP/J8uiFkqxkcWndWi+W7bXdPbt27Q==", + "optional": true + }, + "@rollup/rollup-win32-x64-msvc": { + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.9.tgz", + "integrity": "sha512-AyleYRPU7+rgkMWbEh71fQlrzRfeP6SyMnRf9XX4fCdDPAJumdSBqYEcWPMzVQ4ScAl7E4oFfK0GUVn77xSwbw==", + "optional": true + } + } + }, "@swc/core": { "version": "1.12.14", "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.12.14.tgz", @@ -6665,6 +6915,14 @@ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true }, + "ionicons": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/ionicons/-/ionicons-7.4.0.tgz", + "integrity": "sha512-ZK94MMqgzMCPPMhmk8Ouu6goyVHFIlw/ACP6oe3FrikcI0N7CX0xcwVaEbUc0G/v3W0shI93vo+9ve/KpvcNhQ==", + "requires": { + "@stencil/core": "^4.0.3" + } + }, "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", diff --git a/package.json b/package.json index 1cf3e20..99d12b8 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,8 @@ "preview": "vite preview" }, "dependencies": { + "@ionic/core": "^8.6.5", + "@ionic/react": "^8.6.5", "@reduxjs/toolkit": "^2.8.2", "@tailwindcss/postcss": "^4.1.11", "@types/react-router-dom": "^5.3.3", diff --git a/src/components/Layout/Layout.tsx b/src/components/Layout/Layout.tsx index 3bbbd22..f11bec4 100644 --- a/src/components/Layout/Layout.tsx +++ b/src/components/Layout/Layout.tsx @@ -1,5 +1,6 @@ import React from 'react'; import { useSelector, useDispatch } from 'react-redux'; +import { IonApp, IonHeader, IonToolbar, IonTitle, IonContent, IonFooter, IonChip } from '@ionic/react'; import { selectCurrentSinger, selectIsAdmin, selectControllerName } from '../../redux/authSlice'; import { logout } from '../../redux/authSlice'; import { ActionButton } from '../common'; @@ -18,63 +19,55 @@ const Layout: React.FC = ({ children }) => { }; return ( -
- {/* Header */} -
-
-
- {/* Logo/Title */} + + + +
-

- 🎀 Karaoke App -

+ 🎀 Karaoke App {controllerName && ( Party: {controllerName} )}
- - {/* User Info & Logout */} -
- {currentSinger && ( -
-
- {currentSinger} - {isAdmin && ( - - Admin - - )} -
- - Logout - -
- )} + + + {/* User Info & Logout */} + {currentSinger && ( +
+
+ {currentSinger} + {isAdmin && ( + + Admin + + )} +
+ + Logout +
-
-
-
+ )} + + - {/* Main Content */} -
+ {children} -
+ - {/* Footer */} -
-
+ +

🎡 Powered by Firebase Realtime Database

-
-
-
+ + + ); }; diff --git a/src/components/Navigation/Navigation.tsx b/src/components/Navigation/Navigation.tsx index 5581b2c..2a9be16 100644 --- a/src/components/Navigation/Navigation.tsx +++ b/src/components/Navigation/Navigation.tsx @@ -1,42 +1,80 @@ import React from 'react'; -import { NavLink } from 'react-router-dom'; +import { IonTabs, IonTabBar, IonTabButton, IonLabel, IonIcon } from '@ionic/react'; +import { list, search, heart, add, mic, documentText, time, trophy, people } from 'ionicons/icons'; +import { useLocation, useNavigate } from 'react-router-dom'; const Navigation: React.FC = () => { + const location = useLocation(); + const navigate = useNavigate(); + const navItems = [ - { path: '/queue', label: 'Queue', icon: 'πŸ“‹' }, - { path: '/search', label: 'Search', icon: 'πŸ”' }, - { path: '/favorites', label: 'Favorites', icon: '❀️' }, - { path: '/new-songs', label: 'New Songs', icon: 'πŸ†•' }, - { path: '/artists', label: 'Artists', icon: '🎀' }, - { path: '/song-lists', label: 'Song Lists', icon: 'πŸ“' }, - { path: '/history', label: 'History', icon: '⏰' }, - { path: '/top-played', label: 'Top 100', icon: 'πŸ†' }, - { path: '/singers', label: 'Singers', icon: 'πŸ‘₯' }, + { path: '/queue', label: 'Queue', icon: list }, + { path: '/search', label: 'Search', icon: search }, + { path: '/favorites', label: 'Favorites', icon: heart }, + { path: '/new-songs', label: 'New Songs', icon: add }, + { path: '/artists', label: 'Artists', icon: mic }, + { path: '/song-lists', label: 'Song Lists', icon: documentText }, + { path: '/history', label: 'History', icon: time }, + { path: '/top-played', label: 'Top 100', icon: trophy }, + { path: '/singers', label: 'Singers', icon: people }, ]; + // For mobile, show bottom tabs with main features + const mobileNavItems = [ + { path: '/queue', label: 'Queue', icon: list }, + { path: '/search', label: 'Search', icon: search }, + { path: '/favorites', label: 'Favorites', icon: heart }, + { path: '/history', label: 'History', icon: time }, + ]; + + // Check if we're on mobile (you can adjust this breakpoint) + const isMobile = window.innerWidth < 768; + + const currentItems = isMobile ? mobileNavItems : navItems; + return ( - + <> + {isMobile ? ( + + + {currentItems.map((item) => ( + navigate(item.path)} + > + + {item.label} + + ))} + + + ) : ( + + )} + ); }; diff --git a/src/components/common/ActionButton.tsx b/src/components/common/ActionButton.tsx index b7a74f2..76dd45f 100644 --- a/src/components/common/ActionButton.tsx +++ b/src/components/common/ActionButton.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import { IonButton } from '@ionic/react'; import type { ActionButtonProps } from '../../types'; const ActionButton: React.FC = ({ @@ -9,46 +10,43 @@ const ActionButton: React.FC = ({ disabled = false, className = '' }) => { - const getVariantStyles = () => { + const getVariant = () => { switch (variant) { case 'primary': - return 'bg-blue-600 hover:bg-blue-700 text-white'; + return 'primary'; case 'secondary': - return 'bg-gray-200 hover:bg-gray-300 text-gray-800'; + return 'medium'; case 'danger': - return 'bg-red-600 hover:bg-red-700 text-white'; + return 'danger'; default: - return 'bg-blue-600 hover:bg-blue-700 text-white'; + return 'primary'; } }; - const getSizeStyles = () => { + const getSize = () => { switch (size) { case 'sm': - return 'px-2 py-1 text-xs'; + return 'small'; case 'md': - return 'px-3 py-2 text-sm'; + return 'default'; case 'lg': - return 'px-4 py-2 text-base'; + return 'large'; default: - return 'px-3 py-2 text-sm'; + return 'default'; } }; return ( - + ); }; diff --git a/src/components/common/EmptyState.tsx b/src/components/common/EmptyState.tsx index 8013080..0e760bf 100644 --- a/src/components/common/EmptyState.tsx +++ b/src/components/common/EmptyState.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import { IonContent } from '@ionic/react'; import type { EmptyStateProps } from '../../types'; const EmptyState: React.FC = ({ @@ -8,26 +9,28 @@ const EmptyState: React.FC = ({ action }) => { return ( -
- {icon && ( -
- {icon} -
- )} -

- {title} -

- {message && ( -

- {message} -

- )} - {action && ( -
- {action} -
- )} -
+ +
+ {icon && ( +
+ {icon} +
+ )} +

+ {title} +

+ {message && ( +

+ {message} +

+ )} + {action && ( +
+ {action} +
+ )} +
+
); }; diff --git a/src/components/common/PlayerControls.tsx b/src/components/common/PlayerControls.tsx index 1c8ecd8..473caac 100644 --- a/src/components/common/PlayerControls.tsx +++ b/src/components/common/PlayerControls.tsx @@ -1,4 +1,6 @@ import React from 'react'; +import { IonCard, IonCardContent, IonChip, IonIcon } from '@ionic/react'; +import { play, pause, stop } from 'ionicons/icons'; import ActionButton from './ActionButton'; import { useAppSelector } from '../../redux'; import { selectPlayerState, selectIsAdmin, selectQueue } from '../../redux'; @@ -70,63 +72,73 @@ const PlayerControls: React.FC = ({ className = '' }) => { console.log('PlayerControls - currentState:', currentState); console.log('PlayerControls - hasSongsInQueue:', hasSongsInQueue); + const getStateColor = () => { + switch (currentState) { + case PlayerState.playing: + return 'success'; + case PlayerState.paused: + return 'warning'; + default: + return 'medium'; + } + }; + return ( -
-
-
-

Player Controls

- - {currentState} - -
-
- -
- {currentState === PlayerState.playing ? ( - - ⏸️ Pause - - ) : ( - - ▢️ Play - - )} - - {currentState !== PlayerState.stopped && ( - - ⏹️ Stop - - )} -
- -
- Admin controls - Only visible to admin users - {!hasSongsInQueue && ( -
- Add songs to queue to enable playback controls + + +
+
+

Player Controls

+ + {currentState} +
- )} -
-
+
+ +
+ {currentState === PlayerState.playing ? ( + + + Pause + + ) : ( + + + Play + + )} + + {currentState !== PlayerState.stopped && ( + + + Stop + + )} +
+ +
+ Admin controls - Only visible to admin users + {!hasSongsInQueue && ( +
+ Add songs to queue to enable playback controls +
+ )} +
+ + ); }; diff --git a/src/components/common/SongItem.tsx b/src/components/common/SongItem.tsx index 7b8d4ac..8c3ba18 100644 --- a/src/components/common/SongItem.tsx +++ b/src/components/common/SongItem.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import { IonItem, IonLabel } from '@ionic/react'; import ActionButton from './ActionButton'; import type { SongItemProps } from '../../types'; @@ -132,11 +133,8 @@ const SongItem: React.FC = ({ }; return ( -
-
+ +

{song.title}

@@ -154,12 +152,12 @@ const SongItem: React.FC = ({ Played {song.count} times

)} -
+ -
+
{renderActionPanel()}
-
+ ); }; diff --git a/src/components/common/Toast.tsx b/src/components/common/Toast.tsx index 60f72e6..96b6832 100644 --- a/src/components/common/Toast.tsx +++ b/src/components/common/Toast.tsx @@ -1,4 +1,5 @@ -import React, { useEffect, useState } from 'react'; +import React, { useState } from 'react'; +import { IonToast } from '@ionic/react'; import type { ToastProps } from '../../types'; const Toast: React.FC = ({ @@ -7,50 +8,41 @@ const Toast: React.FC = ({ duration = 3000, onClose }) => { - const [isVisible, setIsVisible] = useState(true); + const [isOpen, setIsOpen] = useState(true); - useEffect(() => { - const timer = setTimeout(() => { - setIsVisible(false); - setTimeout(onClose, 300); // Wait for fade out animation - }, duration); + const handleDismiss = () => { + setIsOpen(false); + onClose(); + }; - return () => clearTimeout(timer); - }, [duration, onClose]); - - const getTypeStyles = () => { + const getColor = () => { switch (type) { case 'success': - return 'bg-green-500 text-white'; + return 'success'; case 'error': - return 'bg-red-500 text-white'; + return 'danger'; case 'info': default: - return 'bg-blue-500 text-white'; + return 'primary'; } }; return ( -
-
- {message} - -
-
+ ); }; diff --git a/src/features/Queue/Queue.tsx b/src/features/Queue/Queue.tsx index a5962b1..304571e 100644 --- a/src/features/Queue/Queue.tsx +++ b/src/features/Queue/Queue.tsx @@ -1,5 +1,7 @@ import React from 'react'; -import { SongItem, EmptyState, ActionButton, PlayerControls } from '../../components/common'; +import { IonList, IonItem, IonItemSliding, IonItemOptions, IonItemOption, IonIcon, IonLabel, IonChip } from '@ionic/react'; +import { trash, arrowUp, arrowDown } from 'ionicons/icons'; +import { EmptyState, ActionButton, PlayerControls } from '../../components/common'; import { useQueue } from '../../hooks'; import { useAppSelector } from '../../redux'; import { selectQueue, selectPlayerState } from '../../redux'; @@ -11,7 +13,6 @@ const Queue: React.FC = () => { queueStats, canReorder, handleRemoveFromQueue, - handleToggleFavorite, handleMoveUp, handleMoveDown, } = useQueue(); @@ -73,67 +74,79 @@ const Queue: React.FC = () => { } /> ) : ( -
+ {queueItems.map((queueItem, index) => { console.log(`Queue item ${index}: order=${queueItem.order}, key=${queueItem.key}`); + const canDelete = index === 0 ? canDeleteFirstItem : true; + return ( -
- {/* Order Number */} -
- {queueItem.order} -
+ + + {/* Order Number */} +
+ {queueItem.order} +
- {/* Song Info */} -
- handleRemoveFromQueue(queueItem) - } - onToggleFavorite={() => handleToggleFavorite(queueItem.song)} - isAdmin={canReorder} - /> -
+ {/* Song Info */} + +

+ {queueItem.song.title} +

+

+ {queueItem.song.artist} +

+
+ + {queueItem.singer.name} + + {queueItem.isCurrentUser && ( + + You + + )} +
+
- {/* Singer Info */} -
-
{queueItem.singer.name}
-
- {queueItem.isCurrentUser ? '(You)' : ''} -
-
- - {/* Admin Controls */} - {canReorder && ( -
- {queueItem.order > 2 && ( - handleMoveUp(queueItem)} - variant="secondary" - size="sm" - > - ↑ - + {/* Admin Controls */} + {canReorder && ( +
+ {queueItem.order > 2 && ( + handleMoveUp(queueItem)} + variant="secondary" + size="sm" + > + + + )} + {queueItem.order > 1 && queueItem.order < queueItems.length && ( + handleMoveDown(queueItem)} + variant="secondary" + size="sm" + > + + + )} +
)} - {queueItem.order > 1 && queueItem.order < queueItems.length && ( - handleMoveDown(queueItem)} - variant="secondary" - size="sm" + + + {/* Swipe Actions */} + {canDelete && ( + + handleRemoveFromQueue(queueItem)} > - ↓ - - )} -
- )} -
- ); + + + + )} + + ); })} -
+ )}
diff --git a/src/index.css b/src/index.css index b5c61c9..02d604f 100644 --- a/src/index.css +++ b/src/index.css @@ -1,3 +1,17 @@ +/* Ionic CSS imports */ +@import '@ionic/react/css/core.css'; +@import '@ionic/react/css/normalize.css'; +@import '@ionic/react/css/structure.css'; +@import '@ionic/react/css/typography.css'; +@import '@ionic/react/css/display.css'; +@import '@ionic/react/css/padding.css'; +@import '@ionic/react/css/float-elements.css'; +@import '@ionic/react/css/text-alignment.css'; +@import '@ionic/react/css/text-transformation.css'; +@import '@ionic/react/css/flex-utils.css'; +@import '@ionic/react/css/display.css'; + +/* Tailwind CSS */ @tailwind base; @tailwind components; @tailwind utilities; diff --git a/src/main.tsx b/src/main.tsx index 9d070ed..7d86ffd 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,10 +1,14 @@ import { StrictMode } from 'react' import { createRoot } from 'react-dom/client' +import { setupIonicReact } from '@ionic/react' import './index.css' import App from './App.tsx' import { Provider } from 'react-redux'; import { store } from './redux/store'; +// Initialize Ionic React +setupIonicReact(); + createRoot(document.getElementById('root')!).render(