Moved from Solidjs to Nextjs for better compatibility with docs & blogs stuff
6
website/.eslintrc.json
Executable file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"extends": [
|
||||
"next/core-web-vitals",
|
||||
"prettier"
|
||||
]
|
||||
}
|
37
website/.gitignore
vendored
Normal file → Executable file
@ -1,2 +1,35 @@
|
||||
node_modules
|
||||
dist
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# local env files
|
||||
.env*.local
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
|
35
website/README.md
Normal file → Executable file
@ -1,3 +1,34 @@
|
||||
# Website
|
||||
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
|
||||
|
||||
The official Website of Spotube
|
||||
## Getting Started
|
||||
|
||||
First, run the development server:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
# or
|
||||
yarn dev
|
||||
```
|
||||
|
||||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||
|
||||
You can start editing the page by modifying `pages/index.tsx`. The page auto-updates as you edit the file.
|
||||
|
||||
[API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.ts`.
|
||||
|
||||
The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages.
|
||||
|
||||
## Learn More
|
||||
|
||||
To learn more about Next.js, take a look at the following resources:
|
||||
|
||||
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
||||
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
||||
|
||||
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
|
||||
|
||||
## Deploy on Vercel
|
||||
|
||||
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
||||
|
||||
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
|
||||
|
123
website/components/AdDetector.tsx
Normal file
@ -0,0 +1,123 @@
|
||||
import {
|
||||
Button,
|
||||
Heading,
|
||||
Modal,
|
||||
ModalBody,
|
||||
ModalContent,
|
||||
ModalFooter,
|
||||
ModalHeader,
|
||||
ModalOverlay,
|
||||
Stack,
|
||||
Text,
|
||||
useDisclosure,
|
||||
VStack,
|
||||
} from "@chakra-ui/react";
|
||||
import { FC, ReactNode, useEffect, useState } from "react";
|
||||
|
||||
const AdDetector: FC<{ children: ReactNode }> = ({ children }) => {
|
||||
const [adBlockEnabled, setAdBlockEnabled] = useState(false);
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
const [joke, setJoke] = useState<Record<string, any>>({});
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const googleAdUrl =
|
||||
"https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js";
|
||||
try {
|
||||
await fetch(new Request(googleAdUrl));
|
||||
} catch (e) {
|
||||
setAdBlockEnabled(true);
|
||||
setJoke(
|
||||
await (
|
||||
await fetch(
|
||||
"https://v2.jokeapi.dev/joke/Any?blacklistFlags=racist,sexist"
|
||||
)
|
||||
).json()
|
||||
);
|
||||
onOpen();
|
||||
}
|
||||
})();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal isOpen={isOpen} onClose={onClose}>
|
||||
<ModalOverlay />
|
||||
<ModalContent mt="5" mx="3">
|
||||
<ModalHeader>Support the Creator💚</ModalHeader>
|
||||
<ModalBody>
|
||||
<p>
|
||||
Open source developers work really hard to provide the best,
|
||||
secure & efficient software experience for you & people all around
|
||||
the world. Most of the time we work without any wages at all but
|
||||
we need minimum support to live & these <b> Ads Helps Us</b> earn
|
||||
the minimum wage that we need to live.{" "}
|
||||
<Text color="green.500" fontWeight="bold" textAlign="justify">
|
||||
So, please support Spotube by disabling the AdBlocker on this
|
||||
page or by sponsoring or donating to our collectives directly
|
||||
</Text>
|
||||
</p>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button onClick={() => window.location.reload()}>
|
||||
Reload without AdBlocker
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
{!adBlockEnabled ? (
|
||||
children
|
||||
) : (
|
||||
<Stack
|
||||
direction="column"
|
||||
w="100vw"
|
||||
h="100vh"
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
p="5"
|
||||
>
|
||||
<Heading></Heading>
|
||||
<VStack spacing="2" alignItems="flex-start">
|
||||
<Heading size="sm">Here's something interesting:</Heading>
|
||||
<Heading size="md">
|
||||
{joke.joke ?? (
|
||||
<>
|
||||
<p>{joke.setup}</p>
|
||||
<p>{joke.delivery}</p>
|
||||
</>
|
||||
)}
|
||||
</Heading>
|
||||
</VStack>
|
||||
<VStack justifySelf="flex-end">
|
||||
<Heading
|
||||
mt="10"
|
||||
size={{
|
||||
base: "lg",
|
||||
lg: "xl",
|
||||
}}
|
||||
maxW="700px"
|
||||
textAlign="justify"
|
||||
lineHeight="1.5"
|
||||
>
|
||||
Be grateful for all the favors you get. But don't let it
|
||||
become a pile of debt. Try returning them as soon as you can.
|
||||
You'll feel relieved
|
||||
</Heading>
|
||||
<Heading
|
||||
size={{
|
||||
lg: "lg",
|
||||
base: "md",
|
||||
}}
|
||||
alignSelf="flex-end"
|
||||
>
|
||||
- Kingkor Roy Tirtho
|
||||
</Heading>
|
||||
</VStack>
|
||||
</Stack>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default AdDetector;
|
48
website/components/Navbar.tsx
Normal file
@ -0,0 +1,48 @@
|
||||
import {
|
||||
Button,
|
||||
ButtonGroup,
|
||||
Heading,
|
||||
HStack,
|
||||
IconButton,
|
||||
useColorMode,
|
||||
} from "@chakra-ui/react";
|
||||
import NavLink from "next/link";
|
||||
import { GoLightBulb } from "react-icons/go";
|
||||
import { FiSun, } from "react-icons/fi";
|
||||
|
||||
const Navbar = () => {
|
||||
const { colorMode, toggleColorMode } = useColorMode();
|
||||
return (
|
||||
<HStack justifyContent="space-between" as="nav" w="full">
|
||||
<HStack alignItems="center">
|
||||
<NavLink href="/" passHref>
|
||||
<Heading p="2" as="a" size="lg" mr="2">
|
||||
Spotube
|
||||
</Heading>
|
||||
</NavLink>
|
||||
<ButtonGroup>
|
||||
<NavLink href="/other-downloads" passHref>
|
||||
<Button as="a" colorScheme="gray" variant="ghost">
|
||||
Other Downloads
|
||||
</Button>
|
||||
</NavLink>
|
||||
<NavLink href="/about" passHref>
|
||||
<Button as="a" variant="ghost" colorScheme="gray">
|
||||
About
|
||||
</Button>
|
||||
</NavLink>
|
||||
</ButtonGroup>
|
||||
</HStack>
|
||||
<IconButton
|
||||
variant="ghost"
|
||||
icon={colorMode == "light" ? <GoLightBulb /> : <FiSun />}
|
||||
aria-label="Dark Mode Toggle"
|
||||
onClick={() => {
|
||||
toggleColorMode();
|
||||
}}
|
||||
/>
|
||||
</HStack>
|
||||
);
|
||||
};
|
||||
|
||||
export default Navbar;
|
72
website/components/special.tsx
Normal file
@ -0,0 +1,72 @@
|
||||
import Script from "next/script";
|
||||
import { FC, useId } from "react";
|
||||
|
||||
type AdComponent = FC<{
|
||||
slot: string;
|
||||
}>;
|
||||
|
||||
export const DisplayAd: AdComponent = ({ slot }) => {
|
||||
const id = useId();
|
||||
return (
|
||||
<>
|
||||
<ins
|
||||
className="adsbygoogle"
|
||||
style={{ display: "block" }}
|
||||
data-ad-client={process.env.NEXT_PUBLIC_ADSENSE_ID}
|
||||
data-ad-slot={slot}
|
||||
data-ad-format="auto"
|
||||
data-full-width-responsive="true"
|
||||
></ins>
|
||||
<Script
|
||||
id={id + "#" + slot}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `(adsbygoogle = window.adsbygoogle || []).push({});`,
|
||||
}}
|
||||
></Script>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const GridMultiplexAd: AdComponent = ({ slot }) => {
|
||||
const id = useId();
|
||||
return (
|
||||
<>
|
||||
<ins
|
||||
className="adsbygoogle"
|
||||
style={{ display: "block" }}
|
||||
data-ad-format="autorelaxed"
|
||||
data-ad-client={process.env.NEXT_PUBLIC_ADSENSE_ID}
|
||||
data-ad-slot={slot}
|
||||
></ins>
|
||||
<Script
|
||||
id={id + "#" + slot}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `(adsbygoogle = window.adsbygoogle || []).push({});`,
|
||||
}}
|
||||
></Script>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const InFeedAd = () => {
|
||||
const id = useId();
|
||||
return (
|
||||
<>
|
||||
<ins
|
||||
className="adsbygoogle"
|
||||
style={{ display: "block" }}
|
||||
data-ad-format="fluid"
|
||||
data-ad-layout-key="-e5+6n-34-bt+x0"
|
||||
data-ad-client="ca-pub-6419300932495863"
|
||||
data-ad-slot="6460144484"
|
||||
></ins>
|
||||
<Script
|
||||
id={id}
|
||||
strategy="afterInteractive"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `(adsbygoogle = window.adsbygoogle || []).push({});`,
|
||||
}}
|
||||
></Script>
|
||||
</>
|
||||
);
|
||||
};
|
17
website/configurations/gtag.ts
Normal file
@ -0,0 +1,17 @@
|
||||
export const GA_TRACKING_ID = process.env.NEXT_PUBLIC_GA_ID;
|
||||
|
||||
// https://developers.google.com/analytics/devguides/collection/gtagjs/pages
|
||||
export const pageview = (url: any) => {
|
||||
(window as any).gtag("config", GA_TRACKING_ID, {
|
||||
page_path: url,
|
||||
});
|
||||
};
|
||||
|
||||
// https://developers.google.com/analytics/devguides/collection/gtagjs/events
|
||||
export const event = ({ action, category, label, value }: any) => {
|
||||
(window as any).gtag("event", action, {
|
||||
event_category: category,
|
||||
event_label: label,
|
||||
value: value,
|
||||
});
|
||||
};
|
26
website/hooks/usePlatform.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export enum Platform {
|
||||
linux = "Linux",
|
||||
windows = "Windows",
|
||||
mac = "Mac",
|
||||
android = "Android",
|
||||
}
|
||||
|
||||
export function usePlatform(): Platform {
|
||||
const [platform, setPlatform] = useState(Platform.linux);
|
||||
|
||||
useEffect(() => {
|
||||
const platform = (
|
||||
((navigator as unknown as any).userAgentData?.platform as
|
||||
| string
|
||||
| undefined) ?? navigator.platform
|
||||
).toLowerCase();
|
||||
|
||||
if (platform.includes("windows")) setPlatform(Platform.windows);
|
||||
else if (platform.includes("mac")) setPlatform(Platform.mac);
|
||||
else if (platform.includes("android")) setPlatform(Platform.android);
|
||||
}, []);
|
||||
|
||||
return platform;
|
||||
}
|
@ -1,52 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
sizes="180x180"
|
||||
href="/src/assets/apple-touch-icon.png"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="32x32"
|
||||
href="/src/assets/favicon-32x32.png"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="16x16"
|
||||
href="/src/assets/favicon-16x16.png"
|
||||
/>
|
||||
<link rel="manifest" href="/src/assets/site.webmanifest" />
|
||||
<title>Spotube</title>
|
||||
<script
|
||||
async
|
||||
src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-6419300932495863"
|
||||
crossorigin="anonymous"
|
||||
></script>
|
||||
<!-- Global site tag (gtag.js) - Google Analytics -->
|
||||
<script
|
||||
async
|
||||
src="https://www.googletagmanager.com/gtag/js?id=G-L593ZMVP33"
|
||||
></script>
|
||||
<script>
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag() {
|
||||
dataLayer.push(arguments);
|
||||
}
|
||||
gtag("js", new Date());
|
||||
|
||||
gtag("config", "G-L593ZMVP33");
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
|
||||
<script src="/src/index.tsx" type="module"></script>
|
||||
</body>
|
||||
</html>
|
13
website/misc/MarkdownComponentDefs.tsx
Normal file
@ -0,0 +1,13 @@
|
||||
import { Link as Anchor, Heading, Text, chakra } from "@chakra-ui/react";
|
||||
|
||||
export const MarkdownComponentDefs = {
|
||||
a: (props: any) => <Anchor {...props} color="blue.500" />,
|
||||
h1: (props: any) => <Heading {...props} size="xl" mt="5" mb="1.5" />,
|
||||
h2: (props: any) => <Heading {...props} size="lg" mt="5" mb="1.5" />,
|
||||
h3: (props: any) => <Heading {...props} size="md" mt="5" mb="1.5" />,
|
||||
h4: (props: any) => <Heading {...props} size="sm" />,
|
||||
h5: (props: any) => <Heading {...props} size="xs" />,
|
||||
h6: (props: any) => <Heading {...props} size="xs" />,
|
||||
p: (props: any) => <Text {...props} />,
|
||||
li: (props: any) => <chakra.li {...props} ml="4" />,
|
||||
};
|
5
website/next-env.d.ts
vendored
Executable file
@ -0,0 +1,5 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/basic-features/typescript for more information.
|
7
website/next.config.js
Executable file
@ -0,0 +1,7 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
reactStrictMode: true,
|
||||
swcMinify: true,
|
||||
}
|
||||
|
||||
module.exports = nextConfig
|
@ -1,30 +1,34 @@
|
||||
{
|
||||
"name": "vite-template-solid",
|
||||
"version": "0.0.0",
|
||||
"description": "",
|
||||
"name": "website",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "vite",
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"serve": "vite preview"
|
||||
},
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"typescript": "^4.7.4",
|
||||
"vite": "^3.0.0",
|
||||
"vite-plugin-solid": "^2.3.0"
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hope-ui/solid": "^0.6.2",
|
||||
"@chakra-ui/react": "^2.2.4",
|
||||
"@chakra-ui/theme-tools": "^2.0.5",
|
||||
"@emotion/react": "^11",
|
||||
"@emotion/styled": "^11",
|
||||
"@octokit/rest": "^19.0.3",
|
||||
"@stitches/core": "^1.2.8",
|
||||
"isomorphic-fetch": "^3.0.0",
|
||||
"remark-gfm": "^3.0.1",
|
||||
"solid-app-router": "^0.4.1",
|
||||
"solid-cached-resource": "^0.3.0",
|
||||
"solid-icons": "^0.5.0",
|
||||
"solid-js": "^1.4.7",
|
||||
"solid-markdown": "^1.2.0",
|
||||
"solid-transition-group": "^0.0.10"
|
||||
"framer-motion": "^6",
|
||||
"next": "12.2.2",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-icons": "^4.4.0",
|
||||
"react-markdown": "^8.0.3",
|
||||
"remark-gfm": "^3.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "18.0.5",
|
||||
"@types/react": "18.0.15",
|
||||
"@types/react-dom": "18.0.6",
|
||||
"eslint": "8.20.0",
|
||||
"eslint-config-next": "12.2.2",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"typescript": "4.7.4"
|
||||
}
|
||||
}
|
||||
|
125
website/pages/_app.tsx
Executable file
@ -0,0 +1,125 @@
|
||||
import "../styles/globals.css";
|
||||
import type { AppProps } from "next/app";
|
||||
import {
|
||||
ChakraProvider,
|
||||
extendTheme,
|
||||
withDefaultColorScheme,
|
||||
} from "@chakra-ui/react";
|
||||
import Navbar from "components/Navbar";
|
||||
import { mode } from "@chakra-ui/theme-tools";
|
||||
import Head from "next/head";
|
||||
import Script from "next/script";
|
||||
import * as gtag from "configurations/gtag";
|
||||
import { useRouter } from "next/router";
|
||||
import { useEffect } from "react";
|
||||
import AdDetector from "components/AdDetector";
|
||||
|
||||
const customTheme = extendTheme(
|
||||
{
|
||||
styles: {
|
||||
global: (props: any) => ({
|
||||
body: {
|
||||
bg: mode("white", "#171717")(props),
|
||||
},
|
||||
}),
|
||||
},
|
||||
colors: {
|
||||
green: {
|
||||
50: "#d4f3df",
|
||||
100: "#b7ecca",
|
||||
200: "#9be4b4",
|
||||
300: "#61d48a",
|
||||
400: "#45cd74",
|
||||
500: "#32ba62",
|
||||
600: "#2b9e53",
|
||||
700: "#238144",
|
||||
800: "#1b6435",
|
||||
900: "#134826",
|
||||
},
|
||||
components: {
|
||||
Link: {
|
||||
baseStyle: {
|
||||
color: "green",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
withDefaultColorScheme({ colorScheme: "green" })
|
||||
);
|
||||
|
||||
function MyApp({ Component, pageProps }: AppProps) {
|
||||
const router = useRouter();
|
||||
useEffect(() => {
|
||||
const handleRouteChange = (url: string) => {
|
||||
gtag.pageview(url);
|
||||
};
|
||||
router.events.on("routeChangeComplete", handleRouteChange);
|
||||
router.events.on("hashChangeComplete", handleRouteChange);
|
||||
return () => {
|
||||
router.events.off("routeChangeComplete", handleRouteChange);
|
||||
router.events.off("hashChangeComplete", handleRouteChange);
|
||||
};
|
||||
}, [router.events]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Script
|
||||
async
|
||||
onError={(e) => {
|
||||
console.error("Script failed to load", e);
|
||||
}}
|
||||
src={`https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=${process.env.NEXT_PUBLIC_ADSENSE_ID}`}
|
||||
crossOrigin="anonymous"
|
||||
/>
|
||||
{/* Global Site Tag (gtag.js) - Google Analytics */}
|
||||
<Script
|
||||
strategy="afterInteractive"
|
||||
src={`https://www.googletagmanager.com/gtag/js?id=${gtag.GA_TRACKING_ID}`}
|
||||
/>
|
||||
<Script
|
||||
id="gtag-init"
|
||||
strategy="afterInteractive"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag(){dataLayer.push(arguments);}
|
||||
gtag('js', new Date());
|
||||
gtag('config', '${gtag.GA_TRACKING_ID}', {
|
||||
page_path: window.location.pathname,
|
||||
});
|
||||
`,
|
||||
}}
|
||||
/>
|
||||
<ChakraProvider theme={customTheme}>
|
||||
<Head>
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
sizes="180x180"
|
||||
href="/apple-touch-icon.png"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="32x32"
|
||||
href="/favicon-32x32.png"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="16x16"
|
||||
href="/favicon-16x16.png"
|
||||
/>
|
||||
<link rel="manifest" href="/site.webmanifest" />
|
||||
<title>Spotube</title>
|
||||
</Head>
|
||||
<AdDetector>
|
||||
<Navbar />
|
||||
<Component {...pageProps} />
|
||||
</AdDetector>
|
||||
</ChakraProvider>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default MyApp;
|
13
website/pages/api/hello.ts
Executable file
@ -0,0 +1,13 @@
|
||||
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
|
||||
import type { NextApiRequest, NextApiResponse } from 'next'
|
||||
|
||||
type Data = {
|
||||
name: string
|
||||
}
|
||||
|
||||
export default function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse<Data>
|
||||
) {
|
||||
res.status(200).json({ name: 'John Doe' })
|
||||
}
|
106
website/pages/index.tsx
Executable file
@ -0,0 +1,106 @@
|
||||
import {
|
||||
Heading,
|
||||
Menu,
|
||||
ButtonGroup,
|
||||
Button,
|
||||
IconButton,
|
||||
MenuItem,
|
||||
VStack,
|
||||
MenuButton,
|
||||
Link as Anchor,
|
||||
chakra,
|
||||
MenuList,
|
||||
} from "@chakra-ui/react";
|
||||
import { FaCaretDown } from "react-icons/fa";
|
||||
import { DisplayAd } from "../components/special";
|
||||
import { Platform, usePlatform } from "../hooks/usePlatform";
|
||||
|
||||
const baseURL = "https://github.com/KRTirtho/spotube/releases/latest/download/";
|
||||
|
||||
const DownloadLinks = Object.freeze({
|
||||
[Platform.linux]: [
|
||||
{ name: "deb", url: baseURL + "Spotube-linux-x86_64.deb" },
|
||||
{ name: "tar", url: baseURL + "Spotube-linux-x86_64.tar.xz" },
|
||||
{ name: "AppImage", url: baseURL + "Spotube-linux-x86_64.AppImage" },
|
||||
],
|
||||
[Platform.android]: [
|
||||
{
|
||||
name: "apk",
|
||||
url: baseURL + "Spotube-android-all-arch.apk",
|
||||
},
|
||||
],
|
||||
[Platform.mac]: [{ name: "dmg", url: baseURL + "Spotube-macos-x86_64.dmg" }],
|
||||
[Platform.windows]: [
|
||||
{ name: "exe", url: baseURL + "Spotube-windows-x86_64-setup.exe" },
|
||||
{ name: "nupkg", url: baseURL + "Spotube-windows-x86_64.nupkg " },
|
||||
],
|
||||
});
|
||||
|
||||
const Root = () => {
|
||||
const platform = usePlatform();
|
||||
|
||||
const allPlatforms = Object.entries(Platform)
|
||||
.map(([, value]) => {
|
||||
return DownloadLinks[value].map((s) => ({
|
||||
...s,
|
||||
name: `${value} (.${s.name})`,
|
||||
}));
|
||||
})
|
||||
.flat(1);
|
||||
|
||||
const currentPlatform = DownloadLinks[platform][0];
|
||||
return (
|
||||
<>
|
||||
<VStack spacing="$4" alignItems="stretch">
|
||||
<chakra.section
|
||||
h="60vh"
|
||||
bgColor="#f5f5f5"
|
||||
bgImage="url(/spotube-screenshot-web.jpg)"
|
||||
bgRepeat="no-repeat"
|
||||
bgSize="contain"
|
||||
bgPos="right"
|
||||
>
|
||||
<VStack mt="10" mx="6" spacing="4" alignItems="flex-start">
|
||||
<Heading color="#212121" size="2xl">
|
||||
Spotube
|
||||
</Heading>
|
||||
<Heading color="#212121" size="lg" textAlign="justify" maxW="500px">
|
||||
A fast, modern, lightweight & efficient Spotify Music Client for
|
||||
every platform
|
||||
</Heading>
|
||||
<Menu placement="bottom-end">
|
||||
<ButtonGroup spacing="0.5">
|
||||
<Button
|
||||
variant="solid"
|
||||
as={Anchor}
|
||||
href={currentPlatform.url}
|
||||
_hover={{ textDecoration: "none" }}
|
||||
>
|
||||
Download for {platform} (.{currentPlatform.name})
|
||||
</Button>
|
||||
<MenuButton
|
||||
aria-label="Show More Downloads"
|
||||
as={IconButton}
|
||||
variant="solid"
|
||||
icon={<FaCaretDown />}
|
||||
/>
|
||||
</ButtonGroup>
|
||||
<MenuList>
|
||||
{allPlatforms.map(({ name, url }) => {
|
||||
return (
|
||||
<MenuItem key={url} as={Anchor} href={url}>
|
||||
{name}
|
||||
</MenuItem>
|
||||
);
|
||||
})}
|
||||
</MenuList>
|
||||
</Menu>
|
||||
</VStack>
|
||||
</chakra.section>
|
||||
<DisplayAd slot="9501208974" />
|
||||
</VStack>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Root;
|
36
website/pages/other-downloads/index.tsx
Normal file
@ -0,0 +1,36 @@
|
||||
import { Link as Anchor, Heading, VStack, chakra } from "@chakra-ui/react";
|
||||
import NavLink from "next/link";
|
||||
import { GridMultiplexAd } from "components/special";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
function OtherDownloads() {
|
||||
const router = useRouter();
|
||||
|
||||
return (
|
||||
<>
|
||||
<VStack my="20" mx="5">
|
||||
<Heading size="lg">Download other versions of Spotube</Heading>
|
||||
<chakra.ul pl="5">
|
||||
<li>
|
||||
<NavLink href={router.pathname + "/stable-downloads"} passHref>
|
||||
<Anchor color="blue.500">
|
||||
Download previous versions of Spotube
|
||||
</Anchor>
|
||||
</NavLink>
|
||||
</li>
|
||||
<li>
|
||||
<NavLink href={router.pathname + "/nightly-downloads"} passHref>
|
||||
<Anchor color="blue.500">
|
||||
Download Bleeding Edge Nightly version of Spotube
|
||||
</Anchor>
|
||||
</NavLink>
|
||||
(Nightly releases)
|
||||
</li>
|
||||
</chakra.ul>
|
||||
</VStack>
|
||||
<GridMultiplexAd slot="4575915852" />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default OtherDownloads;
|
87
website/pages/other-downloads/nightly-downloads.tsx
Normal file
@ -0,0 +1,87 @@
|
||||
import {
|
||||
Link as Anchor,
|
||||
Button,
|
||||
Heading,
|
||||
Table,
|
||||
Td,
|
||||
Th,
|
||||
VStack,
|
||||
chakra,
|
||||
Text,
|
||||
Tr,
|
||||
HStack,
|
||||
} from "@chakra-ui/react";
|
||||
import { GridMultiplexAd } from "components/special";
|
||||
import NavLink from "next/link";
|
||||
|
||||
const baseURL =
|
||||
"https://nightly.link/KRTirtho/spotube/workflows/spotube-nightly/build/";
|
||||
|
||||
const DownloadLinks = Object.freeze({
|
||||
Linux: baseURL + "Spotube-Linux-Bundle.zip",
|
||||
Android: baseURL + "Spotube-Android-Bundle.zip",
|
||||
Windows: baseURL + "Spotube-Windows-Bundle.zip",
|
||||
MacOS: baseURL + "Spotube-Macos-Bundle.zip",
|
||||
});
|
||||
|
||||
function NightlyDownloads() {
|
||||
return (
|
||||
<>
|
||||
<VStack>
|
||||
<VStack
|
||||
alignSelf="center"
|
||||
alignItems="flex-start"
|
||||
spacing="4"
|
||||
maxW="500px"
|
||||
overflow="auto"
|
||||
m="5"
|
||||
>
|
||||
<Heading size="2xl">Nightly Release</Heading>
|
||||
<Text>Download latest & most bleeding edge version of Spotube</Text>
|
||||
<Text size="sm" color="red.500" textAlign="justify">
|
||||
Disclaimer!: Nightly versions are untested and not the final version
|
||||
of spotube. So it can consists of many hidden or unknown bugs but at
|
||||
the same time will be opted with latest & greatest features too. So
|
||||
use it at your own risk! If you don't know what you're
|
||||
doing than we recommend you to download the{" "}
|
||||
<NavLink href="/" passHref>
|
||||
<Anchor color="blue.500">latest stable version of</Anchor>
|
||||
</NavLink>{" "}
|
||||
Spotube
|
||||
</Text>
|
||||
<chakra.section
|
||||
border="2px solid"
|
||||
borderColor="gray"
|
||||
borderRadius="md"
|
||||
px="4"
|
||||
py="2"
|
||||
w="100%"
|
||||
>
|
||||
{Object.entries(DownloadLinks).map(([platform, url]) => {
|
||||
const segments = url.split("/");
|
||||
return (
|
||||
<HStack key={url}>
|
||||
<Text w="100px">{platform}</Text>
|
||||
<Anchor
|
||||
overflowWrap="break-word"
|
||||
wordBreak="break-word"
|
||||
w="full"
|
||||
href={url}
|
||||
color="blue.500"
|
||||
>
|
||||
{segments.at(segments.length - 1)?.replace("build", "")}
|
||||
</Anchor>
|
||||
</HStack>
|
||||
);
|
||||
})}
|
||||
</chakra.section>
|
||||
</VStack>
|
||||
<chakra.div w="full">
|
||||
<GridMultiplexAd slot="3192619797" />
|
||||
</chakra.div>
|
||||
</VStack>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default NightlyDownloads;
|
@ -4,20 +4,20 @@ import {
|
||||
AccordionIcon,
|
||||
AccordionItem,
|
||||
AccordionPanel,
|
||||
Anchor,
|
||||
Link as Anchor,
|
||||
Heading,
|
||||
HStack,
|
||||
Skeleton,
|
||||
Text,
|
||||
VStack,
|
||||
} from "@hope-ui/solid";
|
||||
chakra,
|
||||
} from "@chakra-ui/react";
|
||||
import { Octokit, RestEndpointMethodTypes } from "@octokit/rest";
|
||||
import { createCachedResource } from "solid-cached-resource";
|
||||
import SolidMarkdown from "solid-markdown";
|
||||
import { Platform } from "../hooks/usePlatform";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import { Platform } from "hooks/usePlatform";
|
||||
import gfm from "remark-gfm";
|
||||
import { MarkdownComponentDefs } from "../misc/MarkdownComponentDefs";
|
||||
import { DisplayAd } from "../components/special";
|
||||
import { DisplayAd, InFeedAd } from "components/special";
|
||||
import { GetServerSideProps, NextPage } from "next";
|
||||
import { MarkdownComponentDefs } from "misc/MarkdownComponentDefs";
|
||||
|
||||
enum AssetTypes {
|
||||
sums = "sums",
|
||||
@ -27,30 +27,73 @@ enum AssetTypes {
|
||||
android = "android",
|
||||
}
|
||||
|
||||
export const octokit = new Octokit();
|
||||
function StableDownloads() {
|
||||
const [data] = createCachedResource("gh-releases", () => {
|
||||
return octokit.repos.listReleases({
|
||||
export const octokit: Octokit = new Octokit();
|
||||
|
||||
type ReleaseResponse = {
|
||||
id: number;
|
||||
body: string | null | undefined;
|
||||
tag_name: string;
|
||||
assets: {
|
||||
id: number;
|
||||
name: string;
|
||||
browser_download_url: string;
|
||||
}[];
|
||||
}[];
|
||||
|
||||
type Props = {
|
||||
data: ReleaseResponse;
|
||||
};
|
||||
|
||||
export const getServerSideProps: GetServerSideProps<Props> = async ({
|
||||
res,
|
||||
}) => {
|
||||
res.setHeader(
|
||||
"Cache-Control",
|
||||
"public, s-maxage=10, stale-while-revalidate=59"
|
||||
);
|
||||
const { data } = await octokit.repos.listReleases({
|
||||
owner: "KRTirtho",
|
||||
repo: "spotube",
|
||||
});
|
||||
const releaseResponse: ReleaseResponse = data.map((data) => {
|
||||
return {
|
||||
tag_name: data.tag_name,
|
||||
id: data.id,
|
||||
body: data.body,
|
||||
assets: data.assets.map((asset) => ({
|
||||
id: asset.id,
|
||||
name: asset.name,
|
||||
browser_download_url: asset.browser_download_url,
|
||||
})),
|
||||
};
|
||||
});
|
||||
return {
|
||||
props: {
|
||||
data: releaseResponse,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const StableDownloads: NextPage<Props> = ({ data }) => {
|
||||
return (
|
||||
<VStack alignItems="stretch" m="$3">
|
||||
<Heading size="3xl">Previous Versions</Heading>
|
||||
<Text my="$5">
|
||||
<VStack alignItems="stretch" m="3">
|
||||
<Heading size="xl">Previous Versions</Heading>
|
||||
<Text my="5">
|
||||
If any of your version is not working correctly than you can download &
|
||||
use previous versions of Spotube too
|
||||
</Text>
|
||||
<HStack alignItems="flex-start">
|
||||
<VStack alignItems="stretch" spacing="$3" mr="$1">
|
||||
{data.loading &&
|
||||
!data.latest && [
|
||||
<Skeleton h="$6" w="$56" />,
|
||||
...Array.from({ length: 8 }, () => <Skeleton h="$6" w="$96" />),
|
||||
]}
|
||||
{(data.latest ?? data())?.data.map((release, i) => {
|
||||
<HStack alignItems="flex-start" wrap="wrap">
|
||||
<VStack
|
||||
alignItems="stretch"
|
||||
w={{
|
||||
base: "full",
|
||||
sm: "70%",
|
||||
md: "60%",
|
||||
}}
|
||||
spacing="3"
|
||||
mr="1"
|
||||
>
|
||||
{data.map((release, i) => {
|
||||
const releaseSome = release.assets
|
||||
.map((asset) => {
|
||||
const platform =
|
||||
@ -70,46 +113,53 @@ function StableDownloads() {
|
||||
};
|
||||
return (
|
||||
<VStack
|
||||
py="$3"
|
||||
key={release.id}
|
||||
py="3"
|
||||
alignItems="flex-start"
|
||||
borderBottom="1px solid grey"
|
||||
_last={{ borderBottom: "none" }}
|
||||
>
|
||||
<Heading size="xl">
|
||||
<Heading size="md">
|
||||
Version{" "}
|
||||
<Text as="span" color="$success8">
|
||||
<Text as="span" color="green.500">
|
||||
{release.tag_name}
|
||||
</Text>{" "}
|
||||
{i == 0 && "(Latest)"}
|
||||
</Heading>
|
||||
{Object.entries(releaseSome).map(([type, assets]) => {
|
||||
{Object.entries(releaseSome).map(([type, assets], i) => {
|
||||
return (
|
||||
<HStack py="$2" alignItems="flex-start">
|
||||
<HStack key={i} spacing={0} py="2" alignItems="flex-start">
|
||||
<Heading
|
||||
w={90}
|
||||
p="$2"
|
||||
color="$info12"
|
||||
border={`2px solid #404040`}
|
||||
p="2"
|
||||
colorScheme="blue"
|
||||
border="2px solid"
|
||||
borderColor="gray"
|
||||
borderRadius="5px 0 0 5px"
|
||||
borderRight="none"
|
||||
size="sm"
|
||||
>
|
||||
{type[0].toUpperCase() + type.slice(1)}
|
||||
</Heading>
|
||||
<VStack
|
||||
alignItems="flex-start"
|
||||
border={`2px solid #404040`}
|
||||
border="2px solid"
|
||||
borderColor="gray"
|
||||
borderRadius={`0 5px 5px ${
|
||||
assets.length !== 1 ? 5 : 0
|
||||
}px`}
|
||||
w="$72"
|
||||
w="72"
|
||||
>
|
||||
{assets.map((asset) => {
|
||||
return (
|
||||
<Anchor
|
||||
color="$info11"
|
||||
width="$full"
|
||||
p="$2"
|
||||
href={asset.name}
|
||||
key={asset.id}
|
||||
color="blue.500"
|
||||
width="full"
|
||||
p="1.5"
|
||||
href={asset.browser_download_url}
|
||||
target="_blank"
|
||||
referrerPolicy="no-referrer"
|
||||
>
|
||||
{asset.name}
|
||||
</Anchor>
|
||||
@ -119,18 +169,18 @@ function StableDownloads() {
|
||||
</HStack>
|
||||
);
|
||||
})}
|
||||
<Accordion defaultIndex={i}>
|
||||
<Accordion defaultIndex={i} allowToggle>
|
||||
<AccordionItem>
|
||||
<AccordionButton>
|
||||
Release Notes <AccordionIcon />
|
||||
</AccordionButton>
|
||||
<AccordionPanel>
|
||||
<SolidMarkdown
|
||||
<ReactMarkdown
|
||||
components={MarkdownComponentDefs}
|
||||
remarkPlugins={[gfm]}
|
||||
>
|
||||
{release.body ?? ""}
|
||||
</SolidMarkdown>
|
||||
</ReactMarkdown>
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
@ -138,15 +188,22 @@ function StableDownloads() {
|
||||
);
|
||||
})}
|
||||
</VStack>
|
||||
<VStack id="Ad">
|
||||
<chakra.div
|
||||
id="Ad"
|
||||
w={{
|
||||
base: "full",
|
||||
sm: "25%",
|
||||
md: "35%",
|
||||
}}
|
||||
>
|
||||
<DisplayAd slot="1391349310" />
|
||||
<DisplayAd slot="6452104301" />
|
||||
<DisplayAd slot="1199777626" />
|
||||
<DisplayAd slot="2001723409" />
|
||||
</VStack>
|
||||
</chakra.div>
|
||||
</HStack>
|
||||
</VStack>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default StableDownloads;
|
@ -1,2 +0,0 @@
|
||||
/all-versions /index.html 200
|
||||
/about /index.html 200
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 35 KiB |
Before Width: | Height: | Size: 136 KiB After Width: | Height: | Size: 136 KiB |
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 892 B After Width: | Height: | Size: 892 B |
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 323 KiB After Width: | Height: | Size: 323 KiB |
@ -1,146 +0,0 @@
|
||||
import {
|
||||
Button,
|
||||
createDisclosure,
|
||||
Heading,
|
||||
Modal,
|
||||
ModalBody,
|
||||
ModalContent,
|
||||
ModalFooter,
|
||||
ModalHeader,
|
||||
ModalOverlay,
|
||||
Stack,
|
||||
Text,
|
||||
VStack,
|
||||
} from "@hope-ui/solid";
|
||||
import {
|
||||
Component,
|
||||
createEffect,
|
||||
createResource,
|
||||
createSignal,
|
||||
} from "solid-js";
|
||||
import Navbar from "./components/Navbar";
|
||||
import { Route, Router, Routes } from "solid-app-router";
|
||||
import { Root } from "./pages/Root";
|
||||
import OtherDownloads from "./pages/OtherDownloads";
|
||||
import NightlyDownloads from "./pages/NightlyDownloads";
|
||||
import StableDownloads from "./pages/StableDownloads";
|
||||
|
||||
const App: Component = () => {
|
||||
const [adBlockEnabled, setAdBlockEnabled] = createSignal(false);
|
||||
const { isOpen, onOpen, onClose } = createDisclosure();
|
||||
const [joke, setJoke] = createSignal<Record<string, any>>({});
|
||||
|
||||
createEffect(() => {
|
||||
(async () => {
|
||||
const googleAdUrl =
|
||||
"https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js";
|
||||
try {
|
||||
await fetch(new Request(googleAdUrl)).catch((_) =>
|
||||
setAdBlockEnabled(true)
|
||||
);
|
||||
} catch (e) {
|
||||
setAdBlockEnabled(true);
|
||||
} finally {
|
||||
if (adBlockEnabled()) {
|
||||
setJoke(
|
||||
await (
|
||||
await fetch(
|
||||
"https://v2.jokeapi.dev/joke/Any?blacklistFlags=racist,sexist"
|
||||
)
|
||||
).json()
|
||||
);
|
||||
onOpen();
|
||||
}
|
||||
}
|
||||
})();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Router>
|
||||
<Modal opened={isOpen()} onClose={onClose}>
|
||||
<ModalOverlay />
|
||||
<ModalContent mt="$5" mx="$3">
|
||||
<ModalHeader>Support the Creator💚</ModalHeader>
|
||||
<ModalBody>
|
||||
<p>
|
||||
Open source developers work really hard to provide the best,
|
||||
secure & efficient software experience for you & people all around
|
||||
the world. Most of the time we work without any wages at all but
|
||||
we need minimum support to live & these <b> Ads Helps Us</b> earn
|
||||
the minimum wage that we need to live.{" "}
|
||||
<Text color="$success10" fontWeight="bold" textAlign="justify">
|
||||
So, please support Spotube by disabling the AdBlocker on this
|
||||
page or by sponsoring or donating to our collectives directly
|
||||
</Text>
|
||||
</p>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button onClick={() => window.location.reload()}>
|
||||
Reload without AdBlocker
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
{!adBlockEnabled() ? (
|
||||
<VStack alignItems="stretch">
|
||||
<Navbar />
|
||||
<Routes>
|
||||
<Route path="/" component={Root} />
|
||||
<Route path="/other-downloads" component={OtherDownloads} />
|
||||
<Route path="/stable-downloads" component={StableDownloads} />
|
||||
<Route path="/nightly-downloads" component={NightlyDownloads} />
|
||||
</Routes>
|
||||
</VStack>
|
||||
) : (
|
||||
<Stack
|
||||
direction="column"
|
||||
w="100vw"
|
||||
h="100vh"
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
p="$5"
|
||||
>
|
||||
<Heading></Heading>
|
||||
<VStack spacing="$2" alignItems="flex-start">
|
||||
<Heading>Here's something interesting:</Heading>
|
||||
<Heading size="xl">
|
||||
{joke().joke ?? (
|
||||
<>
|
||||
<p>{joke().setup}</p>
|
||||
<p>{joke().delivery}</p>
|
||||
</>
|
||||
)}
|
||||
</Heading>
|
||||
</VStack>
|
||||
<VStack justifySelf="flex-end">
|
||||
<Heading
|
||||
mt="$10"
|
||||
size={{
|
||||
"@lg": "4xl",
|
||||
"@initial": "2xl",
|
||||
}}
|
||||
maxW="700px"
|
||||
textAlign="justify"
|
||||
lineHeight="1.5"
|
||||
>
|
||||
Be grateful for all the favors you get. But don't let it become a
|
||||
pile of debt. Try returning them as soon as you can. You'll feel
|
||||
relieved
|
||||
</Heading>
|
||||
<Heading
|
||||
size={{
|
||||
"@lg": "2xl",
|
||||
"@initial": "lg",
|
||||
}}
|
||||
alignSelf="flex-end"
|
||||
>
|
||||
- Kingkor Roy Tirtho
|
||||
</Heading>
|
||||
</VStack>
|
||||
</Stack>
|
||||
)}
|
||||
</Router>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
Before Width: | Height: | Size: 15 KiB |
@ -1,47 +0,0 @@
|
||||
import {
|
||||
Button,
|
||||
ButtonGroup,
|
||||
Heading,
|
||||
HStack,
|
||||
IconButton,
|
||||
useColorMode,
|
||||
} from "@hope-ui/solid";
|
||||
import { NavLink } from "solid-app-router";
|
||||
import { FaLightbulb } from "solid-icons/fa";
|
||||
import { FiSun } from "solid-icons/fi";
|
||||
|
||||
const Navbar = () => {
|
||||
const { colorMode, toggleColorMode } = useColorMode();
|
||||
return (
|
||||
<HStack py="$2" justifyContent="space-between" as="nav" w="$full">
|
||||
<section>
|
||||
<Heading p="$2" size="3xl" mr="$2" as={NavLink} href="/">
|
||||
Spotube
|
||||
</Heading>
|
||||
<ButtonGroup>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
as={NavLink}
|
||||
href="/other-downloads"
|
||||
>
|
||||
Other Downloads
|
||||
</Button>
|
||||
<Button variant="ghost" size="sm" as={NavLink} href="/about">
|
||||
About
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</section>
|
||||
<IconButton
|
||||
variant="ghost"
|
||||
icon={colorMode() == "dark" ? <FaLightbulb /> : <FiSun />}
|
||||
aria-label="Dark Mode Toggle"
|
||||
onClick={() => {
|
||||
toggleColorMode();
|
||||
}}
|
||||
/>
|
||||
</HStack>
|
||||
);
|
||||
};
|
||||
|
||||
export default Navbar;
|
@ -1,40 +0,0 @@
|
||||
import { Component, createEffect } from "solid-js";
|
||||
|
||||
type AdComponent = Component<{
|
||||
slot: string;
|
||||
}>;
|
||||
|
||||
export const DisplayAd: AdComponent = ({ slot }) => {
|
||||
createEffect(() => {
|
||||
//@ts-ignore
|
||||
(window.adsbygoogle = window.adsbygoogle || []).push({});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<ins
|
||||
class="adsbygoogle"
|
||||
style={{ display: "block" }}
|
||||
data-ad-client="ca-pub-6419300932495863"
|
||||
data-ad-slot={slot}
|
||||
data-ad-format="auto"
|
||||
data-full-width-responsive="true"
|
||||
></ins>
|
||||
);
|
||||
};
|
||||
|
||||
export const GridMultiplexAd: AdComponent = ({ slot }) => {
|
||||
createEffect(() => {
|
||||
//@ts-ignore
|
||||
(window.adsbygoogle = window.adsbygoogle || []).push({});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<ins
|
||||
class="adsbygoogle"
|
||||
style={{ display: "block" }}
|
||||
data-ad-format="autorelaxed"
|
||||
data-ad-client="ca-pub-6419300932495863"
|
||||
data-ad-slot={slot}
|
||||
></ins>
|
||||
);
|
||||
};
|
@ -1,19 +0,0 @@
|
||||
export enum Platform {
|
||||
linux = "Linux",
|
||||
windows = "Windows",
|
||||
mac = "Mac",
|
||||
android = "Android",
|
||||
}
|
||||
|
||||
export function usePlatform(): Platform {
|
||||
const platform = (
|
||||
((navigator as unknown as any).userAgentData?.platform as
|
||||
| string
|
||||
| undefined) ?? navigator.platform
|
||||
).toLowerCase();
|
||||
|
||||
if (platform.includes("windows")) return Platform.windows;
|
||||
else if (platform.includes("mac")) return Platform.mac;
|
||||
else if (platform.includes("android")) return Platform.android;
|
||||
else return Platform.linux;
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||
sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||
monospace;
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
/* @refresh reload */
|
||||
import { render } from "solid-js/web";
|
||||
|
||||
import "./index.css";
|
||||
import App from "./App";
|
||||
import { HopeProvider } from "@hope-ui/solid";
|
||||
|
||||
render(
|
||||
() => (
|
||||
<HopeProvider
|
||||
config={{
|
||||
lightTheme: {
|
||||
colors: {
|
||||
primary1: "#d4f3df",
|
||||
primary2: "#d4f3df",
|
||||
primary3: "#b7ecca",
|
||||
primary4: "#9be4b4",
|
||||
primary5: "#7edc9f",
|
||||
primary6: "#61d48a",
|
||||
primary7: "#45cd74",
|
||||
primary8: "#32ba62",
|
||||
primary9: "#2b9e53",
|
||||
primary10: "#238144",
|
||||
primary11: "#1b6435",
|
||||
primary12: "#134826",
|
||||
},
|
||||
},
|
||||
initialColorMode: "system",
|
||||
}}
|
||||
>
|
||||
<App />
|
||||
</HopeProvider>
|
||||
),
|
||||
document.getElementById("root") as HTMLElement
|
||||
);
|
@ -1,13 +0,0 @@
|
||||
import { Anchor, Heading, Text, hope } from "@hope-ui/solid";
|
||||
|
||||
export const MarkdownComponentDefs = {
|
||||
a: (props: any) => <Anchor {...props} color="$info10" />,
|
||||
h1: (props: any) => <Heading {...props} size="4xl" mt="$5" mb="$1_5" />,
|
||||
h2: (props: any) => <Heading {...props} size="3xl" mt="$5" mb="$1_5" />,
|
||||
h3: (props: any) => <Heading {...props} size="2xl" mt="$5" mb="$1_5" />,
|
||||
h4: (props: any) => <Heading {...props} size="md" />,
|
||||
h5: (props: any) => <Heading {...props} size="lg" />,
|
||||
h6: (props: any) => <Heading {...props} size="md" />,
|
||||
p: (props: any) => <Text {...props} />,
|
||||
li: (props: any) => <hope.li {...props} ml="$4" />,
|
||||
};
|
@ -1,75 +0,0 @@
|
||||
import {
|
||||
Anchor,
|
||||
Button,
|
||||
Heading,
|
||||
Table,
|
||||
Td,
|
||||
Th,
|
||||
Tr,
|
||||
VStack,
|
||||
hope,
|
||||
Text,
|
||||
} from "@hope-ui/solid";
|
||||
import { NavLink } from "solid-app-router";
|
||||
import { GridMultiplexAd } from "../components/special";
|
||||
|
||||
const baseURL =
|
||||
"https://nightly.link/KRTirtho/spotube/workflows/spotube-nightly/build";
|
||||
|
||||
const DownloadLinks = Object.freeze({
|
||||
Linux: baseURL + "Spotube-Linux-Bundle.zip",
|
||||
Android: baseURL + "Spotube-Android-Bundle.zip",
|
||||
Windows: baseURL + "Spotube-Windows-Bundle.zip",
|
||||
MacOS: baseURL + "Spotube-Macos-Bundle.zip",
|
||||
});
|
||||
|
||||
function NightlyDownloads() {
|
||||
return (
|
||||
<VStack
|
||||
mx="$3"
|
||||
alignSelf="center"
|
||||
alignItems="flex-start"
|
||||
spacing="$4"
|
||||
maxW="500px"
|
||||
overflow="auto"
|
||||
>
|
||||
<Heading size="2xl">Nightly Release</Heading>
|
||||
<Text>Download latest & most bleeding edge version of Spotube</Text>
|
||||
<Text size="sm" color="$danger11" textAlign="justify">
|
||||
Disclaimer!: Nightly versions are untested and not the final version of
|
||||
spotube. So it can consists of many hidden or unknown bugs but at the
|
||||
same time will be opted with latest & greatest features too. So use it
|
||||
at your own risk! If you don't know what you're doing than we recommend
|
||||
you to download the{" "}
|
||||
<Anchor as={NavLink} color="$info10" href="/">
|
||||
latest stable version of
|
||||
</Anchor>{" "}
|
||||
Spotube
|
||||
</Text>
|
||||
<hope.section
|
||||
border="2px solid"
|
||||
borderColor="$neutral10"
|
||||
borderRadius="$md"
|
||||
>
|
||||
<Table overflow="auto">
|
||||
{Object.entries(DownloadLinks).map(([platform, url]) => {
|
||||
const segments = url.split("/");
|
||||
return (
|
||||
<Tr>
|
||||
<Th>{platform}</Th>
|
||||
<Td>
|
||||
<Button as={Anchor} href={url} variant="ghost">
|
||||
{segments.at(segments.length - 1)?.replace("build", "")}
|
||||
</Button>
|
||||
</Td>
|
||||
</Tr>
|
||||
);
|
||||
})}
|
||||
</Table>
|
||||
</hope.section>
|
||||
<GridMultiplexAd slot="3192619797" />
|
||||
</VStack>
|
||||
);
|
||||
}
|
||||
|
||||
export default NightlyDownloads;
|
@ -1,27 +0,0 @@
|
||||
import { Anchor, Heading, VStack } from "@hope-ui/solid";
|
||||
import { NavLink } from "solid-app-router";
|
||||
import { GridMultiplexAd } from "../components/special";
|
||||
|
||||
function OtherDownloads() {
|
||||
return (
|
||||
<VStack alignItems="flex-start" ml="$10" mt="$20">
|
||||
<Heading size="2xl">Download other versions of Spotube</Heading>
|
||||
<ul>
|
||||
<li>
|
||||
<Anchor color="$info10" as={NavLink} href="/stable-downloads">
|
||||
Download previous versions of Spotube
|
||||
</Anchor>
|
||||
</li>
|
||||
<li>
|
||||
<Anchor color="$info10" as={NavLink} href="/nightly-downloads">
|
||||
Download Bleeding Edge Nightly version of Spotube
|
||||
</Anchor>{" "}
|
||||
(Nightly releases)
|
||||
</li>
|
||||
</ul>
|
||||
<GridMultiplexAd slot="4575915852" />
|
||||
</VStack>
|
||||
);
|
||||
}
|
||||
|
||||
export default OtherDownloads;
|
@ -1,101 +0,0 @@
|
||||
import {
|
||||
Heading,
|
||||
Menu,
|
||||
ButtonGroup,
|
||||
Button,
|
||||
Anchor,
|
||||
MenuTrigger,
|
||||
IconButton,
|
||||
MenuContent,
|
||||
MenuItem,
|
||||
VStack,
|
||||
hope,
|
||||
} from "@hope-ui/solid";
|
||||
import { FaSolidCaretDown } from "solid-icons/fa";
|
||||
import { DisplayAd } from "../components/special";
|
||||
import { Platform, usePlatform } from "../hooks/usePlatform";
|
||||
|
||||
const baseURL = "https://github.com/KRTirtho/spotube/releases/latest/download/";
|
||||
|
||||
const DownloadLinks = Object.freeze({
|
||||
[Platform.linux]: [
|
||||
{ name: "deb", url: baseURL + "Spotube-linux-x86_64.deb" },
|
||||
{ name: "tar", url: baseURL + "Spotube-linux-x86_64.tar.xz" },
|
||||
{ name: "AppImage", url: baseURL + "Spotube-linux-x86_64.AppImage" },
|
||||
],
|
||||
[Platform.android]: [
|
||||
{
|
||||
name: "apk",
|
||||
url: baseURL + "Spotube-android-all-arch.apk",
|
||||
},
|
||||
],
|
||||
[Platform.mac]: [{ name: "dmg", url: baseURL + "Spotube-macos-x86_64.dmg" }],
|
||||
[Platform.windows]: [
|
||||
{ name: "exe", url: baseURL + "Spotube-windows-x86_64-setup.exe" },
|
||||
{ name: "nupkg", url: baseURL + "Spotube-windows-x86_64.nupkg " },
|
||||
],
|
||||
});
|
||||
|
||||
export const Root = () => {
|
||||
const platform = usePlatform();
|
||||
|
||||
const allPlatforms = Object.entries(Platform)
|
||||
.map(([key, value]) => {
|
||||
return DownloadLinks[value].map((s) => ({
|
||||
...s,
|
||||
name: `${value} (.${s.name})`,
|
||||
}));
|
||||
})
|
||||
.flat(1);
|
||||
|
||||
const currentPlatform = DownloadLinks[platform][0];
|
||||
return (
|
||||
<VStack spacing="$4" alignItems="stretch">
|
||||
<hope.section
|
||||
h="60vh"
|
||||
backgroundColor="#f5f5f5"
|
||||
style={{
|
||||
"background-image": "url(/src/assets/spotube-screenshot-web.jpg)",
|
||||
"background-repeat": "no-repeat",
|
||||
"background-size": "contain",
|
||||
"background-position": "right",
|
||||
}}
|
||||
>
|
||||
<VStack mt="$10" mx="$6" spacing="$4" alignItems="flex-start">
|
||||
<Heading color="#212121" size="5xl">
|
||||
Spotube
|
||||
</Heading>
|
||||
<Heading color="#212121" size="2xl" textAlign="justify" maxW="500px">
|
||||
A fast, modern, lightweight & efficient Spotify Music Client for
|
||||
every platform
|
||||
</Heading>
|
||||
<Menu placement="bottom-end">
|
||||
<ButtonGroup spacing="$0_5">
|
||||
<Button
|
||||
variant="subtle"
|
||||
as={Anchor}
|
||||
href={currentPlatform.url}
|
||||
_hover={{ textDecoration: "none" }}
|
||||
>
|
||||
Download for {platform} (.{currentPlatform.name})
|
||||
</Button>
|
||||
<MenuTrigger as={IconButton} variant="subtle">
|
||||
<FaSolidCaretDown />
|
||||
</MenuTrigger>
|
||||
</ButtonGroup>
|
||||
<MenuContent>
|
||||
{allPlatforms.map(({ name, url }) => {
|
||||
return (
|
||||
<MenuItem as={Anchor} href={url}>
|
||||
{name}
|
||||
</MenuItem>
|
||||
);
|
||||
})}
|
||||
</MenuContent>
|
||||
</Menu>
|
||||
</VStack>
|
||||
</hope.section>
|
||||
<DisplayAd slot="9501208974" />
|
||||
</VStack>
|
||||
);
|
||||
};
|
16
website/styles/globals.css
Executable file
@ -0,0 +1,16 @@
|
||||
html,
|
||||
body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
|
||||
Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
28
website/tsconfig.json
Normal file → Executable file
@ -1,15 +1,23 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "node",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"esModuleInterop": true,
|
||||
"jsx": "preserve",
|
||||
"jsxImportSource": "solid-js",
|
||||
"types": ["vite/client"],
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noEmit": true,
|
||||
"isolatedModules": true
|
||||
}
|
||||
"declaration": true,
|
||||
"sourceMap": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
"baseUrl": "."
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
@ -1,17 +0,0 @@
|
||||
import { defineConfig } from "vite";
|
||||
import solidPlugin from "vite-plugin-solid";
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [solidPlugin()],
|
||||
server: {
|
||||
port: 3000,
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
"node-fetch": "isomorphic-fetch",
|
||||
},
|
||||
},
|
||||
build: {
|
||||
target: "esnext",
|
||||
},
|
||||
});
|