Added some layout stuff

This commit is contained in:
Fergal Moran
2023-05-10 12:00:27 +01:00
parent ac6e20e495
commit d95770e870
22 changed files with 1013 additions and 69 deletions

View File

@@ -7,16 +7,12 @@ await import("./src/env.mjs");
/** @type {import("next").NextConfig} */
const config = {
reactStrictMode: true,
/**
* If you have `experimental: { appDir: true }` set, then you must comment the below `i18n` config
* out.
*
* @see https://github.com/vercel/next.js/issues/41980
*/
i18n: {
locales: ["en"],
defaultLocale: "en",
experimental: {
appDir: true,
},
// i18n: {
// locales: ["en"],
// defaultLocale: "en",
// },
};
export default config;

View File

@@ -18,6 +18,7 @@
"@trpc/next": "^10.18.0",
"@trpc/react-query": "^10.18.0",
"@trpc/server": "^10.18.0",
"classnames": "^2.3.2",
"install": "^0.13.0",
"next": "13.3.4",
"react": "18.2.0",
@@ -32,17 +33,21 @@
"@types/prettier": "^2.7.2",
"@types/react": "^18.0.28",
"@types/react-dom": "^18.0.11",
"@types/uuid": "^9.0.1",
"@typescript-eslint/eslint-plugin": "^5.56.0",
"@typescript-eslint/parser": "^5.56.0",
"autoprefixer": "^10.4.14",
"eslint": "^8.36.0",
"eslint-config-next": "^13.2.4",
"formik": "^2.2.9",
"postcss": "^8.4.21",
"prettier": "^2.8.6",
"prettier-plugin-tailwindcss": "^0.2.6",
"prisma": "^4.13.0",
"react-dropzone": "^14.2.3",
"tailwindcss": "^3.3.0",
"typescript": "^5.0.2"
"typescript": "^5.0.2",
"uuid": "^9.0.0"
},
"ct3aMetadata": {
"initVersion": "7.12.2"

123
pnpm-lock.yaml generated
View File

@@ -25,6 +25,12 @@ dependencies:
'@trpc/server':
specifier: ^10.18.0
version: 10.18.0
classnames:
specifier: ^2.3.2
version: 2.3.2
crypto:
specifier: ^1.0.1
version: 1.0.1
install:
specifier: ^0.13.0
version: 0.13.0
@@ -63,6 +69,9 @@ devDependencies:
'@types/react-dom':
specifier: ^18.0.11
version: 18.0.11
'@types/uuid':
specifier: ^9.0.1
version: 9.0.1
'@typescript-eslint/eslint-plugin':
specifier: ^5.56.0
version: 5.56.0(@typescript-eslint/parser@5.56.0)(eslint@8.36.0)(typescript@5.0.2)
@@ -78,6 +87,9 @@ devDependencies:
eslint-config-next:
specifier: ^13.2.4
version: 13.2.4(eslint@8.36.0)(typescript@5.0.2)
formik:
specifier: ^2.2.9
version: 2.2.9(react@18.2.0)
postcss:
specifier: ^8.4.21
version: 8.4.21
@@ -90,12 +102,18 @@ devDependencies:
prisma:
specifier: ^4.13.0
version: 4.13.0
react-dropzone:
specifier: ^14.2.3
version: 14.2.3(react@18.2.0)
tailwindcss:
specifier: ^3.3.0
version: 3.3.0(postcss@8.4.21)
typescript:
specifier: ^5.0.2
version: 5.0.2
uuid:
specifier: ^9.0.0
version: 9.0.0
packages:
@@ -389,14 +407,14 @@ packages:
dependencies:
asn1js: 3.0.5
pvtsutils: 1.3.2
tslib: 2.4.1
tslib: 2.5.0
dev: false
/@peculiar/json-schema@1.1.12:
resolution: {integrity: sha512-coUfuoMeIB7B8/NMekxaDzLhaYmp0HZNPEjYRm9goRou8UZIC3z21s0sL9AWoCw4EG876QyO3kYrc61WNF9B/w==}
engines: {node: '>=8.0.0'}
dependencies:
tslib: 2.4.1
tslib: 2.5.0
dev: false
/@peculiar/webcrypto@1.4.1:
@@ -406,7 +424,7 @@ packages:
'@peculiar/asn1-schema': 2.3.6
'@peculiar/json-schema': 1.1.12
pvtsutils: 1.3.2
tslib: 2.4.1
tslib: 2.5.0
webcrypto-core: 1.7.7
dev: false
@@ -679,6 +697,10 @@ packages:
'@types/node': 18.15.5
dev: false
/@types/uuid@9.0.1:
resolution: {integrity: sha512-rFT3ak0/2trgvp4yYZo5iKFEPsET7vKydKF+VRCxlQ9bpheehyAJH89dAkaLEq/j/RZXJIqcgsmPJKUP1Z28HA==}
dev: true
/@typescript-eslint/eslint-plugin@5.56.0(@typescript-eslint/parser@5.56.0)(eslint@8.36.0)(typescript@5.0.2):
resolution: {integrity: sha512-ZNW37Ccl3oMZkzxrYDUX4o7cnuPgU+YrcaYXzsRtLB16I1FR5SHMqga3zGsaSliZADCWo2v8qHWqAYIj8nWCCg==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -929,7 +951,7 @@ packages:
dependencies:
pvtsutils: 1.3.2
pvutils: 1.1.3
tslib: 2.4.1
tslib: 2.5.0
dev: false
/ast-types-flow@0.0.7:
@@ -940,6 +962,11 @@ packages:
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
dev: false
/attr-accept@2.2.2:
resolution: {integrity: sha512-7prDjvt9HmqiZ0cl5CRjtS84sEyhsHP2coDkaZKRKVfCDo9s7iw7ChVmar78Gu9pC4SoR/28wFu/G5JJhTnqEg==}
engines: {node: '>=4'}
dev: true
/autoprefixer@10.4.14(postcss@8.4.21):
resolution: {integrity: sha512-FQzyfOsTlwVzjHxKEqRIAdJx9niO6VCBCoEwax/VLSoQF29ggECcPuBqUMZ+u8jCZOPSy8b8/8KnuFbp0SaFZQ==}
engines: {node: ^10 || ^12 || >=14}
@@ -1089,6 +1116,10 @@ packages:
fsevents: 2.3.2
dev: true
/classnames@2.3.2:
resolution: {integrity: sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==}
dev: false
/client-only@0.0.1:
resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==}
dev: false
@@ -1141,6 +1172,11 @@ packages:
which: 2.0.2
dev: true
/crypto@1.0.1:
resolution: {integrity: sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig==}
deprecated: This package is no longer supported. It's now a built-in Node module. If you've depended on crypto, you should switch to the one that's built-in.
dev: false
/cssesc@3.0.0:
resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}
engines: {node: '>=4'}
@@ -1209,6 +1245,11 @@ packages:
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
dev: true
/deepmerge@2.2.1:
resolution: {integrity: sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA==}
engines: {node: '>=0.10.0'}
dev: true
/deepmerge@4.2.2:
resolution: {integrity: sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==}
engines: {node: '>=0.10.0'}
@@ -1283,7 +1324,7 @@ packages:
resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==}
dependencies:
no-case: 3.0.4
tslib: 2.4.1
tslib: 2.5.0
dev: false
/electron-to-chromium@1.4.385:
@@ -1744,6 +1785,13 @@ packages:
flat-cache: 3.0.4
dev: true
/file-selector@0.6.0:
resolution: {integrity: sha512-QlZ5yJC0VxHxQQsQhXvBaC7VRJ2uaxTf+Tfpu4Z/OcVQJVpZO+DGU0rkoVW5ce2SccxugvpBJoMvUs59iILYdw==}
engines: {node: '>= 12'}
dependencies:
tslib: 2.5.0
dev: true
/fill-range@7.0.1:
resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==}
engines: {node: '>=8'}
@@ -1786,6 +1834,21 @@ packages:
mime-types: 2.1.35
dev: false
/formik@2.2.9(react@18.2.0):
resolution: {integrity: sha512-LQLcISMmf1r5at4/gyJigGn0gOwFbeEAlji+N9InZF6LIMXnFNkO42sCI8Jt84YZggpD4cPWObAZaxpEFtSzNA==}
peerDependencies:
react: '>=16.8.0'
dependencies:
deepmerge: 2.2.1
hoist-non-react-statics: 3.3.2
lodash: 4.17.21
lodash-es: 4.17.21
react: 18.2.0
react-fast-compare: 2.0.4
tiny-warning: 1.0.3
tslib: 1.14.1
dev: true
/fraction.js@4.2.0:
resolution: {integrity: sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==}
dev: true
@@ -1982,6 +2045,12 @@ packages:
function-bind: 1.1.1
dev: true
/hoist-non-react-statics@3.3.2:
resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==}
dependencies:
react-is: 16.13.1
dev: true
/human-signals@2.1.0:
resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==}
engines: {node: '>=10.17.0'}
@@ -2314,10 +2383,18 @@ packages:
p-locate: 5.0.0
dev: true
/lodash-es@4.17.21:
resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==}
dev: true
/lodash.merge@4.6.2:
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
dev: true
/lodash@4.17.21:
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
dev: true
/loose-envify@1.4.0:
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
hasBin: true
@@ -2327,7 +2404,7 @@ packages:
/lower-case@2.0.2:
resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==}
dependencies:
tslib: 2.4.1
tslib: 2.5.0
dev: false
/lru-cache@6.0.0:
@@ -2468,7 +2545,7 @@ packages:
resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==}
dependencies:
lower-case: 2.0.2
tslib: 2.4.1
tslib: 2.5.0
dev: false
/node-fetch-native@1.0.1:
@@ -2857,7 +2934,7 @@ packages:
/pvtsutils@1.3.2:
resolution: {integrity: sha512-+Ipe2iNUyrZz+8K/2IOo+kKikdtfhRKzNpQbruF2URmqPtoqAs8g3xS7TJvFF2GcPXjh7DkqMnpVveRFq4PgEQ==}
dependencies:
tslib: 2.4.1
tslib: 2.5.0
dev: false
/pvutils@1.1.3:
@@ -2889,6 +2966,22 @@ packages:
scheduler: 0.23.0
dev: false
/react-dropzone@14.2.3(react@18.2.0):
resolution: {integrity: sha512-O3om8I+PkFKbxCukfIR3QAGftYXDZfOE2N1mr/7qebQJHs7U+/RSL/9xomJNpRg9kM5h9soQSdf0Gc7OHF5Fug==}
engines: {node: '>= 10.13'}
peerDependencies:
react: '>= 16.8 || 18.0.0'
dependencies:
attr-accept: 2.2.2
file-selector: 0.6.0
prop-types: 15.8.1
react: 18.2.0
dev: true
/react-fast-compare@2.0.4:
resolution: {integrity: sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==}
dev: true
/react-icons@4.8.0(react@18.2.0):
resolution: {integrity: sha512-N6+kOLcihDiAnj5Czu637waJqSnwlMNROzVZMhfX68V/9bu9qHaMIJC4UdozWoOk57gahFCNHwVvWzm0MTzRjg==}
peerDependencies:
@@ -2914,7 +3007,6 @@ packages:
engines: {node: '>=0.10.0'}
dependencies:
loose-envify: 1.4.0
dev: false
/read-cache@1.0.0:
resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==}
@@ -3055,7 +3147,7 @@ packages:
resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==}
dependencies:
dot-case: 3.0.4
tslib: 2.4.1
tslib: 2.5.0
dev: false
/snakecase-keys@3.2.1:
@@ -3279,6 +3371,10 @@ packages:
any-promise: 1.3.0
dev: true
/tiny-warning@1.0.3:
resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==}
dev: true
/titleize@3.0.0:
resolution: {integrity: sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ==}
engines: {node: '>=12'}
@@ -3415,6 +3511,11 @@ packages:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
dev: true
/uuid@9.0.0:
resolution: {integrity: sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==}
hasBin: true
dev: true
/webcrypto-core@1.7.7:
resolution: {integrity: sha512-7FjigXNsBfopEj+5DV2nhNpfic2vumtjjgPmeDKk45z+MJwXKKfhPB7118Pfzrmh4jqOMST6Ch37iPAHoImg5g==}
dependencies:
@@ -3422,7 +3523,7 @@ packages:
'@peculiar/json-schema': 1.1.12
asn1js: 3.0.5
pvtsutils: 1.3.2
tslib: 2.4.1
tslib: 2.5.0
dev: false
/which-boxed-primitive@1.0.2:

3
src/app/globals.css Normal file
View File

@@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

24
src/app/layout.tsx Normal file
View File

@@ -0,0 +1,24 @@
import Navbar from "@/lib/components/layout/Navbar";
import { ClerkProvider } from "@clerk/nextjs";
import "./globals.css";
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<ClerkProvider>
<html lang="en">
<body>
<Navbar />
<div className="pt-16">{children}</div>
</body>
</html>
</ClerkProvider>
);
}
export const metadata = {
title: "Mixy::Boos",
description: "Robot Powered Mixes",
};

View File

@@ -0,0 +1,8 @@
import MixCreateComponent from "@/lib/components/mix/create/MixCreateComponent";
import React from "react";
const MixCreatePage = () => {
return <MixCreateComponent />;
};
export default MixCreatePage;

11
src/app/page.tsx Normal file
View File

@@ -0,0 +1,11 @@
import React from "react";
const Home = () => {
return (
<main className="flex min-h-screen flex-col items-center justify-center">
<div></div>
</main>
);
};
export default Home;

View File

@@ -1,3 +1,4 @@
"use client";
import React from "react";
import { GiHamburgerMenu } from "react-icons/gi";
import { RiFindReplaceLine } from "react-icons/ri";

View File

@@ -0,0 +1,177 @@
"use client";
import React from "react";
import { Sidebar, TextInput, Avatar } from "flowbite-react";
import { useRouter } from "next/navigation";
import Link from "next/link";
import classNames from "classnames";
import { UserModel } from "@lib/data/models";
import {
HiShoppingBag,
HiPencil,
HiLogin,
HiThumbUp,
HiOutlineDeviceMobile,
HiPhotograph,
HiPresentationChartBar,
} from "react-icons/hi";
import { FiTrendingUp } from "react-icons/fi";
import { BsPersonBoundingBox, BsPersonVcard } from "react-icons/bs";
import {
MdDynamicFeed,
MdFavoriteBorder,
MdMusicVideo,
MdOutlineRecentActors,
MdQueryStats,
} from "react-icons/md";
import { CgSandClock } from "react-icons/cg";
import { BiCategoryAlt } from "react-icons/bi";
import Loading from "../../widgets/Loading";
interface IIDashboardSidebarProps {
user: UserModel | undefined;
}
const DashboardSidebar = ({ user }: IIDashboardSidebarProps) => {
const router = useRouter();
const _sidebarItemClick = (path: string | undefined): void => {
if (!path) return;
if (path.includes("dashboard")) {
} else {
router.push(path);
}
};
return !user ? (
<Loading />
) : (
<div className="h-full w-60 space-y-2 p-3 dark:bg-slate-900 dark:text-gray-100">
<div className="flex items-center space-x-4 p-2">
{user.profileImage && (
<UserImage src={user.profileImage} status={"offline"} size={"md"} />
)}
<div>
<h2 className="text-lg font-semibold">{user.displayName}</h2>
<span className="flex items-center space-x-1">
<a
rel="noopener noreferrer"
href="#"
className="text-xs hover:underline dark:text-gray-400"
>
{user.biography}
</a>
</span>
</div>
</div>
<div className="divide-y divide-gray-700">
<ul className="space-y-1 pb-4 pt-2 text-sm">
<li className="dark:bg-slate-800 dark:text-gray-50">
<Link
rel="noopener noreferrer"
href="dashboard/shows"
className="flex items-center space-x-3 rounded-md p-2"
>
<BsPersonVcard className="h-5 w-5 fill-current dark:text-gray-400" />
<span>My Shows</span>
</Link>
</li>
<li className="dark:bg-slate-800 dark:text-gray-50">
<Link
rel="noopener noreferrer"
href="dashboard/mixes"
className="flex items-center space-x-3 rounded-md p-2"
>
<BsPersonBoundingBox className="h-5 w-5 fill-current dark:text-gray-400" />
<span>My Mixes</span>
</Link>
</li>
<li className="dark:bg-slate-800 dark:text-gray-50">
<Link
rel="noopener noreferrer"
href="stats"
className="flex items-center space-x-3 rounded-md p-2"
>
<MdQueryStats className="h-5 w-5 fill-current dark:text-gray-400" />
<span>Stats</span>
</Link>
</li>
</ul>
<ul className="space-y-1 pb-2 pt-4 text-sm">
<li className="dark:bg-slate-800 dark:text-gray-50">
<Link
rel="noopener noreferrer"
href="feed"
className="flex items-center space-x-3 rounded-md p-2"
>
<MdDynamicFeed className="h-5 w-5 fill-current dark:text-gray-400" />
<span>Feed</span>
</Link>
</li>
<li className="dark:bg-slate-800 dark:text-gray-50">
<Link
rel="noopener noreferrer"
href="/shows/new"
className="flex items-center space-x-3 rounded-md p-2"
>
<MdOutlineRecentActors className="h-5 w-5 fill-current dark:text-gray-400" />
<span>New Shows</span>
</Link>
</li>
<li className="dark:bg-slate-800 dark:text-gray-50">
<Link
rel="noopener noreferrer"
href="favourites"
className="flex items-center space-x-3 rounded-md p-2"
>
<MdFavoriteBorder className="h-5 w-5 fill-current dark:text-gray-400" />
<span>Favourites</span>
</Link>
</li>
<li className="dark:bg-slate-800 dark:text-gray-50">
<Link
rel="noopener noreferrer"
href="/dashboard/listen-later"
className="flex items-center space-x-3 rounded-md p-2"
>
<CgSandClock className="h-5 w-5 fill-current dark:text-gray-400" />
<span>Listen Later</span>
</Link>
</li>
</ul>{" "}
<ul className="space-y-1 pb-2 pt-4 text-sm">
<li className="dark:bg-slate-800 dark:text-gray-50">
<Link
rel="noopener noreferrer"
href="categories"
className="flex items-center space-x-3 rounded-md p-2"
>
<BiCategoryAlt className="h-5 w-5 fill-current dark:text-gray-400" />
<span>Categories</span>
</Link>
</li>
<li className="dark:bg-slate-800 dark:text-gray-50">
<Link
rel="noopener noreferrer"
href="trending"
className="flex items-center space-x-3 rounded-md p-2"
>
<FiTrendingUp className="h-5 w-5 fill-current dark:text-gray-400" />
<span>Trending</span>
</Link>
</li>
<li className="dark:bg-slate-800 dark:text-gray-50">
<Link
rel="noopener noreferrer"
href="livenow"
className="flex items-center space-x-3 rounded-md p-2"
>
<MdMusicVideo className="h-5 w-5 fill-current dark:text-gray-400" />
<span>Live Now</span>
</Link>
</li>
</ul>
</div>
</div>
);
};
export default DashboardSidebar;

View File

@@ -0,0 +1,99 @@
interface ISidebarItem {
id: number;
title: string;
path?: string;
icon?: any;
}
import { HiAcademicCap } from "react-icons/hi2";
import {
HiThumbUp,
HiLogin,
HiPencil,
HiShoppingBag,
HiOutlineDeviceMobile,
HiPresentationChartBar,
} from "react-icons/hi";
const sidebarItems: ISidebarItem[] = [
{
id: 0,
icon: HiShoppingBag,
path: "dashboard/shows",
title: "My Shows",
},
{
id: 2,
icon: HiPencil,
path: "/dashboard/mixes",
title: "My Mixes",
},
{
id: 3,
icon: HiLogin,
path: "/stats",
title: "Stats",
},
{
id: 4,
title: "SPACER",
},
{
id: 5,
icon: HiPencil,
path: "feed",
title: "Feed",
},
{
id: 6,
icon: HiPencil,
path: "/shows/new",
title: "New Shows",
},
{
id: 8,
title: "SPACER",
},
{
id: 9,
icon: HiThumbUp,
path: "/favourites",
title: "Favourites",
},
{
id: 10,
icon: HiAcademicCap,
path: "/dashboard/listen-later",
title: "Listen Later",
},
{
id: 11,
icon: HiShoppingBag,
path: "history",
title: "History",
},
{
id: 12,
title: "SPACER",
},
{
id: 13,
icon: HiOutlineDeviceMobile,
path: "/categories",
title: "Categories",
},
{
id: 14,
icon: "HiPhotograph",
path: "trending",
title: "Trending",
},
{
id: 15,
icon: HiPresentationChartBar,
path: "/livenow",
title: "Live Now",
},
];
export default sidebarItems;

View File

@@ -0,0 +1,91 @@
"use client";
import React from "react";
import { useRouter } from "next/navigation";
import { v4 as uuidv4 } from "uuid";
import { MdOutlineErrorOutline } from "react-icons/md";
import PageHeader from "@/lib/components/widgets/PageHeader";
import Progress from "@/lib/components/widgets/Progress";
import FileUpload from "@/lib/components/widgets/FileUpload";
import MixCreateDetailsComponent from "./MixCreateDetailsComponent";
enum CreateState {
new,
editing,
done,
error,
}
enum UploadState {
new,
uploading,
processing,
done,
error,
}
const MixCreateComponent = () => {
const [errors, setErrors] = React.useState<string[]>([]);
const [createState, setCreateState] = React.useState(CreateState.new);
const [uploadState, setUploadState] = React.useState(UploadState.new);
const [percentageUploaded, setPercentageUploaded] = React.useState(0);
const [mixId] = React.useState(uuidv4());
const [fileName, setFilename] = React.useState("");
const router = useRouter();
return (
<div className="flex flex-col justify-center space-y-4">
<PageHeader title="Let's create a mix" />
<div className="flex items-center justify-center">
<div className="w-3/4 max-w-2xl">
{errors.length !== 0 && (
<div className="mx-auto flex w-full max-w-lg overflow-hidden rounded-lg bg-white shadow-md dark:bg-slate-800">
<div className="flex w-12 items-center justify-center bg-red-500">
<MdOutlineErrorOutline className="h-6 w-6 fill-current text-white" />
</div>
<div className="-mx-3 px-4 py-2">
<div className="mx-3">
<span className="font-semibold text-red-500 dark:text-red-400">
Ooopsies...
</span>
<p className="text-sm text-gray-600 dark:text-gray-200">
{errors}
</p>
</div>
</div>
</div>
)}
{uploadState === UploadState.uploading && (
<Progress percentage={percentageUploaded} title="Uploading audio" />
)}
{uploadState === UploadState.new && (
<FileUpload
mixId={mixId}
onError={(e) => {
setCreateState(CreateState.error);
setErrors([...errors, e]);
}}
onUploadComplete={() => {
setUploadState(UploadState.done);
}}
onUploadStart={(fileName) => {
setFilename(fileName);
setUploadState(UploadState.uploading);
}}
onUploadProgress={(total, loaded) => {
setPercentageUploaded(Math.round((loaded * 100) / total));
}}
/>
)}
{createState === CreateState.new &&
uploadState !== UploadState.new && (
<MixCreateDetailsComponent
// mix={{ id: mixId, title: fileName } as MixModel}
// onMixCreated={(mix) => {
// setCreateState(mix ? CreateState.done : CreateState.error);
// }}
/>
)}
</div>
</div>
</div>
);
};
export default MixCreateComponent;

View File

@@ -0,0 +1,147 @@
"use client";
import { Formik } from "formik";
import React from "react";
import ImageUpload from "@/lib/components/widgets/ImageUpload";
const MixCreateDetailsComponent = () => {
const [image, setImage] = React.useState<File | undefined>();
return (
<div className="flex space-x-4">
<div className="w-1/2">
<h2 className="mb-2 text-xl font-bold dark:text-white">Mix info</h2>
<Formik
initialValues={{ title: "Fuck", description: "You" }}
onSubmit={(values, { setSubmitting }) => {
alert("Some bang off these yokes");
}}
>
{({
values,
handleChange,
handleBlur,
handleSubmit,
isSubmitting,
}) => (
<form
onSubmit={() => handleSubmit()}
className="flex flex-col border border-gray-300 p-4 text-gray-800 shadow-lg"
>
<div>
<label
htmlFor="title"
className="mb-2 block text-sm font-medium text-gray-900 dark:text-gray-300"
>
Mix title
</label>
<input
autoFocus
className="block w-full rounded-lg border border-gray-300 bg-transparent p-2.5 text-gray-900 focus:border-fuchsia-300 focus:ring-2 focus:ring-fuchsia-50 dark:text-white sm:text-sm"
id="title"
name="title"
spellCheck="false"
placeholder="Mix Title"
type="text"
onChange={handleChange}
onBlur={handleBlur}
value={values.title}
/>
<div className="mt-4">
<label
htmlFor="message"
className="mb-2 block text-sm font-medium text-gray-900 dark:text-gray-400"
>
Description
</label>
<textarea
className="block w-full rounded-lg border border-gray-300 bg-transparent p-2.5 text-gray-900 focus:border-fuchsia-300 focus:ring-2 focus:ring-fuchsia-50 dark:text-white sm:text-sm"
id="description"
name="description"
spellCheck="true"
rows={5}
placeholder="Tell us something about the mix"
onChange={handleChange}
onBlur={handleBlur}
value={values.description}
/>
</div>
<div className="icons m-2 flex text-gray-500">
<svg
className="mr-2 h-7 cursor-pointer rounded-full border p-1 hover:text-gray-700"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z"
/>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"
/>
</svg>
<svg
className="mr-2 h-7 cursor-pointer rounded-full border p-1 hover:text-gray-700"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M14.828 14.828a4 4 0 01-5.656 0M9 10h.01M15 10h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
<svg
className="mr-2 h-7 cursor-pointer rounded-full border p-1 hover:text-gray-700"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M15.172 7l-6.586 6.586a2 2 0 102.828 2.828l6.414-6.586a4 4 0 00-5.656-5.656l-6.415 6.585a6 6 0 108.486 8.486L20.5 13"
/>
</svg>
<div className="count ml-auto text-xs font-semibold text-gray-400">
0/300
</div>
</div>
<div className="buttons flex">
<div className="ml-auto p-1 px-4 ">
<button
type="submit"
disabled={isSubmitting}
title="Create"
></button>
</div>
</div>
</div>
</form>
)}
</Formik>
</div>
<div className="w-1/2 content-center">
<h2 className="mb-2 text-xl font-bold dark:text-white">Mix image</h2>
<ImageUpload
onImageChanged={(image) => {
console.log("MixDetailsForm", "onImageChanged", image);
setImage(image);
}}
/>
</div>
</div>
);
};
export default MixCreateDetailsComponent;

View File

@@ -0,0 +1,80 @@
import { FaFileAudio } from "react-icons/fa";
import { VscCloudUpload } from "react-icons/vsc";
import React from "react";
import { getFileNameFromInput } from "@/lib/services/utils/fileUtils";
interface IFileUploadProps {
mixId: string;
onError: (error: string) => void;
onUploadStart: (fileName: string) => void;
onUploadComplete: () => void;
onUploadProgress: (total: number, loaded: number) => void;
}
const FileUpload = ({
mixId,
onError,
onUploadStart,
onUploadComplete,
onUploadProgress,
}: IFileUploadProps) => {
const startUpload = async (event: React.FormEvent<HTMLInputElement>) => {
if (!event.currentTarget.files) return;
if (!event.currentTarget.files[0]) return;
// const uploadService = new UploadService();
const formData = new FormData();
formData.append("file", event.currentTarget.files[0]);
try {
onUploadStart(getFileNameFromInput(event.currentTarget.files[0].name));
// const result = await uploadService.uploadAudio(
// mixId,
// formData,
// onUploadProgress
// );
onUploadComplete();
} catch (err) {
console.error("Upload", "Error", err);
onError(
"Error uploading file, please refresh your browser and try again!"
);
}
};
return (
<React.Fragment>
<div className="shadow-cocoa-100 rounded-2xl p-4 shadow-md dark:shadow-gray-400">
<div className="flex items-center">
<div className="bg-cocoa-500 dark:bg-cerise-400 inline-flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-lg text-white">
<FaFileAudio className="h-8 w-8" fill="currentColor" />
</div>
<div className="ml-3 flex-shrink-0">
<span className="text-2xl font-bold leading-none text-gray-900 dark:text-gray-200 sm:text-3xl">
Choose your file
</span>
<h3 className="text-base font-normal text-gray-500">
And we&lsquo;ll do our best to process it.
</h3>
</div>
<div className="ml-5 flex w-0 flex-1 items-center justify-end text-base font-bold">
<label className="text-blue border-blue hover:bg-blue flex w-64 cursor-pointer flex-col items-center rounded-lg border bg-white px-4 py-6 uppercase tracking-wide text-gray-600 shadow-lg hover:text-gray-400">
<VscCloudUpload className="h-8 w-8" fill="currentColor" />
<span className="mt-2 text-base leading-normal">Browse</span>
<input
type="file"
className="hidden"
onChange={(e) => {
startUpload(e).catch((err) => {
console.error("FileUpload", "Error starting upload", err);
});
}}
/>
</label>
</div>
</div>
</div>
</React.Fragment>
);
};
export default FileUpload;

View File

@@ -0,0 +1,87 @@
/* eslint-disable @next/next/no-img-element */
import React from "react";
import Dropzone, { DropzoneRef, useDropzone } from "react-dropzone";
type ImageUploadProps = {
onImageChanged: (newImage: File | undefined) => void;
};
const ImageUpload = ({ onImageChanged }: ImageUploadProps) => {
const dropzoneRef = React.createRef<DropzoneRef>();
return (
<Dropzone
accept={{
"image/png": [".png"],
"image/jpg": [".jpg", ".jpeg"],
}}
maxFiles={1}
ref={dropzoneRef}
onDrop={(acceptedFiles) => {
onImageChanged(
acceptedFiles.length !== 0 ? acceptedFiles[0] : undefined
);
}}
>
{({ getRootProps, getInputProps, acceptedFiles }) => {
return (
<div className="container">
<div {...getRootProps({ className: "dropzone" })}>
{acceptedFiles?.length ? (
<div id="preview">
{acceptedFiles[0] && (
<img
className="object-cover"
src={URL.createObjectURL(acceptedFiles[0])}
alt="image preview"
/>
)}
</div>
) : (
<div id="drop">
<div className="flex w-full items-center justify-center">
<label
htmlFor="dropzone-file"
className="dark:hover:bg-bray-800 flex h-64 w-full cursor-pointer flex-col items-center justify-center rounded-lg border-2 border-dashed border-gray-300 bg-gray-50 hover:bg-gray-100 dark:border-gray-600 dark:bg-slate-700 dark:hover:border-gray-500 dark:hover:bg-gray-600"
>
<div className="flex flex-col items-center justify-center pb-6 pt-5">
<svg
aria-hidden="true"
className="mb-3 h-10 w-10 text-gray-400"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"
></path>
</svg>
<p className="mb-2 text-sm text-gray-500 dark:text-gray-400">
<span className="font-semibold">Click to upload</span>
or drag and drop
</p>
<p className="text-xs text-gray-500 dark:text-gray-400">
SVG, PNG, JPG or GIF (MAX. 800x400px)
</p>
</div>
<input
{...getInputProps()}
id="dropzone-file"
type="file"
className="hidden"
/>
</label>
</div>
</div>
)}
</div>
</div>
);
}}
</Dropzone>
);
};
export default ImageUpload;

View File

@@ -0,0 +1,33 @@
import React from "react";
interface ILoadingProps {
message?: string;
}
const Loading = ({ message = "" }: ILoadingProps) => {
return (
<>
<div role="status" className="mr-3 flex flex-row space-x-1">
<svg
aria-hidden="true"
className="mr-2 h-5 w-5 animate-spin fill-blue-600 text-gray-200 dark:text-gray-600"
viewBox="0 0 100 101"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z"
fill="currentColor"
/>
<path
d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z"
fill="currentFill"
/>
</svg>
<span className="sr-only">Loading...</span>
<span className="text-sm text-gray-700">{message}</span>
</div>
</>
);
};
export default Loading;

View File

@@ -0,0 +1,16 @@
import React from "react";
type PageHeaderProps = {
title: string;
buttons?: React.ReactNode[];
};
const PageHeader = ({ title, buttons = [] }: PageHeaderProps) => {
return (
<div className="flex flex-row border-b-2 pb-2 text-gray-800 dark:text-white ">
<h2 className="flex-grow text-2xl font-bold">{title}</h2>
<div id="button-bar">{buttons}</div>
</div>
);
};
export default PageHeader;

View File

@@ -0,0 +1,29 @@
import React from "react";
type ProgressProps = {
title: string;
percentage: number;
};
export const Progress = ({ title, percentage }: ProgressProps) => {
return (
<div className="flex w-full flex-col p-4">
<div className="mb-1 flex justify-between">
<span className="text-base font-medium text-fuchsia-700 dark:text-white">
{title}
</span>
<span className="text-sm font-medium text-fuchsia-700 dark:text-white">
{percentage}%
</span>
</div>
<div className="h-2.5 w-full rounded-full bg-gray-200 dark:bg-slate-700">
<div
className="h-2.5 rounded-full bg-blue-600"
style={{ width: `${percentage}%` }}
/>
</div>
</div>
);
};
export default Progress;

View File

@@ -0,0 +1,49 @@
import Image from "next/image";
import React, { ComponentProps, PropsWithChildren } from "react";
import classNames from "classnames";
export interface AvatarSizes {
xs: string;
sm: string;
md: string;
lg: string;
xl: string;
[key: string]: string;
}
interface IUserImageProps {
src: string;
status?: "online" | "offline" | "donotdisturb" | "gone";
size?: keyof AvatarSizes;
}
const sizes: AvatarSizes = {
xs: "w-4 h-4",
sm: "w-8 h-8",
md: "w-10 h-10",
lg: "w-12 h-14",
xl: "w-14 h-14",
};
const UserImage: React.FC<IUserImageProps> = ({
src,
status = "gone",
size = "md",
}) => {
const imgClassName = classNames(sizes[size]);
return (
<div
className={classNames(
"flex items-center justify-center space-x-4 rounded-full"
)}
>
<div className="relative">
<Image
width={40}
height={40}
src={src}
alt="Profile Image"
className={classNames("rounded-full", imgClassName)}
/>
</div>
</div>
);
};
export default UserImage;

View File

@@ -0,0 +1,15 @@
const getFileExtension = (fileName: string): string =>
fileName.split(".").pop() as string;
const getFileNameFromInput = (fullPath: string): string => {
const startIndex =
fullPath.indexOf("\\") >= 0
? fullPath.lastIndexOf("\\")
: fullPath.lastIndexOf("/");
let filename = fullPath.substring(startIndex);
if (filename.indexOf("\\") === 0 || filename.indexOf("/") === 0) {
filename = filename.substring(1);
}
return filename.replace(/\.[^/.]+$/, "");
};
export { getFileExtension, getFileNameFromInput };

View File

@@ -1,16 +0,0 @@
import { type AppType } from "next/app";
import { api } from "@/lib/utils/api";
import "@/styles/globals.css";
import { ClerkProvider } from "@clerk/nextjs";
const MyApp: AppType = ({ Component, pageProps }) => {
return (
<ClerkProvider {...pageProps}>
<Component {...pageProps} />
</ClerkProvider>
);
};
export default api.withTRPC(MyApp);

View File

@@ -1,26 +0,0 @@
import Navbar from "@/lib/components/layout/Navbar";
import { api } from "@/lib/utils/api";
import { SignInButton, useUser, UserButton } from "@clerk/nextjs";
import { type NextPage } from "next";
import Head from "next/head";
const Home: NextPage = () => {
const user = useUser();
const { data } = api.mix.getAll.useQuery();
return (
<>
<Head>
<title>Mixy::Boos</title>
<meta name="description" content="Robot Powered Mixes" />
<link rel="icon" href="/favicon.ico" />
</Head>
<Navbar />
<main className="flex min-h-screen flex-col items-center justify-center">
<div></div>
</main>
</>
);
};
export default Home;

View File

@@ -1,7 +1,11 @@
{
"compilerOptions": {
"target": "es2017",
"lib": ["dom", "dom.iterable", "esnext"],
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"checkJs": true,
"skipLibCheck": true,
@@ -18,8 +22,15 @@
"noUncheckedIndexedAccess": true,
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
"@/*": [
"./src/*"
]
},
"plugins": [
{
"name": "next"
}
]
},
"include": [
".eslintrc.cjs",
@@ -27,7 +38,10 @@
"**/*.ts",
"**/*.tsx",
"**/*.cjs",
"**/*.mjs"
"**/*.mjs",
".next/types/**/*.ts"
],
"exclude": ["node_modules"]
"exclude": [
"node_modules"
]
}