mirror of
https://github.com/cupcakearmy/fantus.git
synced 2024-12-22 16:26:25 +00:00
update, formatting and vercel
This commit is contained in:
parent
4ee9c1bb8b
commit
801e1b6710
41
.drone.yml
41
.drone.yml
@ -1,41 +0,0 @@
|
|||||||
kind: pipeline
|
|
||||||
name: default
|
|
||||||
|
|
||||||
steps:
|
|
||||||
# - name: build
|
|
||||||
# image: node:alpine
|
|
||||||
# pull: always
|
|
||||||
# commands:
|
|
||||||
# - node -v
|
|
||||||
# - yarn -v
|
|
||||||
# - yarn
|
|
||||||
# - yarn run build
|
|
||||||
|
|
||||||
- name: deploy
|
|
||||||
image: cupcakearmy/drone-deploy
|
|
||||||
pull: always
|
|
||||||
environment:
|
|
||||||
PLUGIN_KEY:
|
|
||||||
from_secret: ssh_key
|
|
||||||
settings:
|
|
||||||
host: fantus.studio
|
|
||||||
user: root
|
|
||||||
port: 1312
|
|
||||||
target: /srv/web/fantus
|
|
||||||
sources:
|
|
||||||
- ./Dockerfile
|
|
||||||
- ./docker-compose.prod.yml
|
|
||||||
- ./yarn.lock
|
|
||||||
- ./package.json
|
|
||||||
- ./tsconfig.json
|
|
||||||
- ./next.config.js
|
|
||||||
- ./components
|
|
||||||
- ./pages
|
|
||||||
- ./public
|
|
||||||
- ./screens
|
|
||||||
- ./styles
|
|
||||||
commands:
|
|
||||||
- docker-compose -f docker-compose.prod.yml up -d --build
|
|
||||||
when:
|
|
||||||
event: push
|
|
||||||
branch: master
|
|
@ -1,3 +0,0 @@
|
|||||||
RECAPTCHA_CLIENT=client_key
|
|
||||||
RECAPTCHA_SERVER=server_key
|
|
||||||
NEXTCLOUD_TOKEN=next_cloud_share_id
|
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -12,3 +12,4 @@ dist
|
|||||||
|
|
||||||
# Config
|
# Config
|
||||||
.env
|
.env
|
||||||
|
.vercel
|
12
Dockerfile
12
Dockerfile
@ -1,12 +0,0 @@
|
|||||||
FROM node:alpine
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
COPY package.json .
|
|
||||||
COPY yarn.lock .
|
|
||||||
RUN yarn
|
|
||||||
|
|
||||||
COPY . ./
|
|
||||||
RUN yarn run build
|
|
||||||
|
|
||||||
CMD [ "yarn", "run", "start" ]
|
|
@ -4,5 +4,4 @@ Website for fantus
|
|||||||
|
|
||||||
## Stack
|
## Stack
|
||||||
|
|
||||||
- nextjs
|
Next.js hosted on Vercel
|
||||||
- directus headless cms
|
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
import React, { useEffect } from 'react'
|
import React, { useEffect } from 'react'
|
||||||
import { PerspectiveCamera, Scene, BufferGeometry, BufferAttribute, ShaderMaterial, Color, Points, WebGLRenderer, Camera } from 'three'
|
import { PerspectiveCamera, Scene, BufferGeometry, BufferAttribute, ShaderMaterial, Color, Points, WebGLRenderer } from 'three'
|
||||||
|
|
||||||
import '../styles/backgorund.styl'
|
import '../styles/backgorund.styl'
|
||||||
|
|
||||||
// const SEPARATION = 150, AMOUNTX = 200, AMOUNTY = 200, HEIGHT = 75
|
|
||||||
const SEPARATION = 150, AMOUNTX = 50, AMOUNTY = 50, HEIGHT = 75
|
const SEPARATION = 150, AMOUNTX = 50, AMOUNTY = 50, HEIGHT = 75
|
||||||
|
|
||||||
let container: HTMLElement | null
|
let container: HTMLElement | null
|
||||||
|
@ -4,9 +4,7 @@ import '../styles/content.styl'
|
|||||||
|
|
||||||
const Content: React.FC = ({ children }) => {
|
const Content: React.FC = ({ children }) => {
|
||||||
return <div className='container'>
|
return <div className='container'>
|
||||||
{/* <div className='content'> */}
|
|
||||||
{children}
|
{children}
|
||||||
{/* </div> */}
|
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,48 +0,0 @@
|
|||||||
version: '3.7'
|
|
||||||
|
|
||||||
networks:
|
|
||||||
traefik:
|
|
||||||
external: true
|
|
||||||
|
|
||||||
services:
|
|
||||||
|
|
||||||
front:
|
|
||||||
build: .
|
|
||||||
restart: unless-stopped
|
|
||||||
ports:
|
|
||||||
- 80
|
|
||||||
networks:
|
|
||||||
- traefik
|
|
||||||
labels:
|
|
||||||
- 'traefik.enable=true'
|
|
||||||
- 'traefik.backend=fantus'
|
|
||||||
- 'traefik.docker.network=traefik'
|
|
||||||
- 'traefik.frontend.rule=Host:fantus.studio'
|
|
||||||
- 'traefik.port=80'
|
|
||||||
|
|
||||||
mysql:
|
|
||||||
image: mysql:5.7
|
|
||||||
restart: unless-stopped
|
|
||||||
env_file: .directus
|
|
||||||
ports:
|
|
||||||
- 3306
|
|
||||||
volumes:
|
|
||||||
- ./data/db:/var/lib/mysql
|
|
||||||
|
|
||||||
directus:
|
|
||||||
image: directus/directus:v8-apache
|
|
||||||
restart: unless-stopped
|
|
||||||
env_file: .directus
|
|
||||||
volumes:
|
|
||||||
- ./data/config:/var/directus/config
|
|
||||||
- ./data/uploads:/var/directus/public/uploads
|
|
||||||
- ./custom.ini:/usr/local/etc/php/conf.d/custom.ini
|
|
||||||
networks:
|
|
||||||
- traefik
|
|
||||||
- default
|
|
||||||
labels:
|
|
||||||
- "traefik.enable=true"
|
|
||||||
- "traefik.port=80"
|
|
||||||
- "traefik.docker.network=traefik"
|
|
||||||
- "traefik.backend=fantus-api"
|
|
||||||
- "traefik.frontend.rule=Host:api.fantus.studio"
|
|
@ -1,10 +0,0 @@
|
|||||||
version: '3.7'
|
|
||||||
|
|
||||||
services:
|
|
||||||
|
|
||||||
front:
|
|
||||||
build: .
|
|
||||||
ports:
|
|
||||||
- 80:80
|
|
||||||
volumes:
|
|
||||||
- ./.next:/app/.next:ro
|
|
@ -1,14 +1,4 @@
|
|||||||
/* jshint esversion:8, asi:true */
|
|
||||||
|
|
||||||
const withStylus = require('@zeit/next-stylus')
|
const withStylus = require('@zeit/next-stylus')
|
||||||
const withCss = require('@zeit/next-css')
|
const withCss = require('@zeit/next-css')
|
||||||
|
|
||||||
const config = require('dotenv').config().parsed || {}
|
module.exports = withCss(withStylus({}))
|
||||||
const { RECAPTCHA_CLIENT, RECAPTCHA_SERVER } = config
|
|
||||||
|
|
||||||
module.exports = withCss(withStylus({
|
|
||||||
publicRuntimeConfig: {
|
|
||||||
RECAPTCHA_CLIENT,
|
|
||||||
},
|
|
||||||
serverRuntimeConfig: config,
|
|
||||||
}))
|
|
||||||
|
@ -6,11 +6,9 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^0.19.1",
|
"axios": "^0.19.1",
|
||||||
"dotenv": "^8.2.0",
|
|
||||||
"formhero": "^0.0.7",
|
"formhero": "^0.0.7",
|
||||||
"formidable": "^1.2.1",
|
"formidable": "^1.2.1",
|
||||||
"next": "^9.1.7",
|
"next": "^10.0.4",
|
||||||
"nodemailer": "^6.4.2",
|
|
||||||
"react": "^16.12.0",
|
"react": "^16.12.0",
|
||||||
"react-dom": "^16.12.0",
|
"react-dom": "^16.12.0",
|
||||||
"tachyons": "^4.11.1",
|
"tachyons": "^4.11.1",
|
||||||
@ -19,12 +17,11 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/formidable": "^1.0.31",
|
"@types/formidable": "^1.0.31",
|
||||||
"@types/node": "^13.1.6",
|
"@types/node": "^13.1.6",
|
||||||
"@types/nodemailer": "^6.4.0",
|
|
||||||
"@types/react": "^16.9.17",
|
"@types/react": "^16.9.17",
|
||||||
"@types/react-dom": "^16.9.4",
|
"@types/react-dom": "^16.9.4",
|
||||||
"@zeit/next-css": "^1.0.1",
|
"@zeit/next-css": "^1.0.1",
|
||||||
"@zeit/next-stylus": "^1.0.1",
|
"@zeit/next-stylus": "^1.0.1",
|
||||||
"stylus": "^0.54.7",
|
"stylus": "^0.54.7",
|
||||||
"typescript": "^3.7.4"
|
"typescript": "^4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@ import React from 'react'
|
|||||||
import App from 'next/app'
|
import App from 'next/app'
|
||||||
import Head from 'next/head'
|
import Head from 'next/head'
|
||||||
import dynamic from 'next/dynamic'
|
import dynamic from 'next/dynamic'
|
||||||
import getConfig from 'next/config'
|
|
||||||
|
|
||||||
import 'tachyons/css/tachyons.min.css'
|
import 'tachyons/css/tachyons.min.css'
|
||||||
import '../styles/app.styl'
|
import '../styles/app.styl'
|
||||||
@ -10,7 +9,6 @@ import Menu from '../screens/menu'
|
|||||||
import Content from '../components/content'
|
import Content from '../components/content'
|
||||||
|
|
||||||
const Background = dynamic(() => import('../components/background'), { ssr: false })
|
const Background = dynamic(() => import('../components/background'), { ssr: false })
|
||||||
const { RECAPTCHA_CLIENT } = getConfig().publicRuntimeConfig
|
|
||||||
|
|
||||||
export default class extends App {
|
export default class extends App {
|
||||||
render() {
|
render() {
|
||||||
@ -24,7 +22,9 @@ export default class extends App {
|
|||||||
<meta name="description" content="fantus - producer, dj, engineer" />
|
<meta name="description" content="fantus - producer, dj, engineer" />
|
||||||
<meta name="keywords" content="dj,producer,mastering,service,free,sets,mix,techno,music,set" />
|
<meta name="keywords" content="dj,producer,mastering,service,free,sets,mix,techno,music,set" />
|
||||||
<link href="https://fonts.googleapis.com/css?family=Space+Mono:400,700&display=swap" rel="stylesheet" />
|
<link href="https://fonts.googleapis.com/css?family=Space+Mono:400,700&display=swap" rel="stylesheet" />
|
||||||
<script src={'https://www.google.com/recaptcha/api.js?render=' + RECAPTCHA_CLIENT}></script>
|
<script
|
||||||
|
src={'https://www.google.com/recaptcha/api.js?render=' + process.env.NEXT_PUBLIC_RECAPTCHA_CLIENT}
|
||||||
|
></script>
|
||||||
</Head>
|
</Head>
|
||||||
<Menu />
|
<Menu />
|
||||||
<Background />
|
<Background />
|
||||||
|
@ -3,11 +3,23 @@ import Document, { Html, Head, Main, NextScript } from 'next/document'
|
|||||||
export default class extends Document {
|
export default class extends Document {
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<Html lang='en'>
|
<Html lang="en">
|
||||||
<Head />
|
<Head />
|
||||||
<body>
|
<body>
|
||||||
<script type="x-shader/x-vertex" id="vertexshader" dangerouslySetInnerHTML={{ __html: `attribute float scale;void main() {vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );gl_PointSize = scale * ( 300.0 / - mvPosition.z );gl_Position = projectionMatrix * mvPosition;}` }}></script>
|
<script
|
||||||
<script type="x-shader/x-fragment" id="fragmentshader" dangerouslySetInnerHTML={{ __html: `uniform vec3 color;void main() {if ( length( gl_PointCoord - vec2( 0.5, 0.5 ) ) > 0.475 ) discard;gl_FragColor = vec4( color, 1.0 );}` }}></script>
|
type="x-shader/x-vertex"
|
||||||
|
id="vertexshader"
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: `attribute float scale;void main() {vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );gl_PointSize = scale * ( 300.0 / - mvPosition.z );gl_Position = projectionMatrix * mvPosition;}`,
|
||||||
|
}}
|
||||||
|
></script>
|
||||||
|
<script
|
||||||
|
type="x-shader/x-fragment"
|
||||||
|
id="fragmentshader"
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: `uniform vec3 color;void main() {if ( length( gl_PointCoord - vec2( 0.5, 0.5 ) ) > 0.475 ) discard;gl_FragColor = vec4( color, 1.0 );}`,
|
||||||
|
}}
|
||||||
|
></script>
|
||||||
<Main />
|
<Main />
|
||||||
<NextScript />
|
<NextScript />
|
||||||
</body>
|
</body>
|
||||||
|
@ -1,33 +1,45 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
const Home = () => {
|
const Home = () => {
|
||||||
return <div className='center color'>
|
return (
|
||||||
<h1 className='ma0'>about</h1>
|
<div className="center color">
|
||||||
<div className='text'>
|
<h1 className="ma0">about</h1>
|
||||||
|
<div className="text">
|
||||||
<p>
|
<p>
|
||||||
yet another producer. because we don't already have enough of them.
|
yet another producer. because we don't already have enough of them.
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
📍based on planet earth
|
📍based on planet earth
|
||||||
<br />
|
<br />
|
||||||
|
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<a target='_blank' href='https://www.mixcloud.com/fantus/'>mixcloud</a>
|
<a target="_blank" href="https://www.mixcloud.com/fantus/">
|
||||||
|
mixcloud
|
||||||
|
</a>
|
||||||
<br />
|
<br />
|
||||||
<a target='_blank' href='https://soundcloud.com/fantus_music'>soundcloud</a>
|
<a target="_blank" href="https://soundcloud.com/fantus_music">
|
||||||
|
soundcloud
|
||||||
|
</a>
|
||||||
<br />
|
<br />
|
||||||
<a target='_blank' href='https://www.instagram.com/fantus.studio/'>instagram</a>
|
<a target="_blank" href="https://www.instagram.com/fantus.studio/">
|
||||||
|
instagram
|
||||||
|
</a>
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
<a target='_blank' href='mailto:hi@fantus.studio'>contact email</a>
|
<a target="_blank" href="mailto:hi@fantus.studio">
|
||||||
|
contact email
|
||||||
|
</a>
|
||||||
</p>
|
</p>
|
||||||
<br />
|
<br />
|
||||||
<p>
|
<p>
|
||||||
website made by 🤖 with ❤️. <a target='_blank' href="https://github.com/cupcakearmy/fantus">source code available here</a>
|
website made by 🤖 with ❤️.{' '}
|
||||||
|
<a target="_blank" href="https://github.com/cupcakearmy/fantus">
|
||||||
|
source code available here
|
||||||
|
</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Home
|
export default Home
|
@ -1,13 +1,10 @@
|
|||||||
import { createReadStream, statSync, unlinkSync, writeFileSync } from 'fs'
|
import { createReadStream, statSync, unlinkSync, writeFileSync } from 'fs'
|
||||||
import { tmpdir } from 'os'
|
import { tmpdir } from 'os'
|
||||||
|
|
||||||
import { NextApiRequest, NextApiResponse } from 'next'
|
import type { NextApiRequest, NextApiResponse } from 'next'
|
||||||
import getConfig from 'next/config'
|
|
||||||
import Formidable from 'formidable'
|
import Formidable from 'formidable'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
|
|
||||||
const { RECAPTCHA_SERVER, NEXTCLOUD_TOKEN } = getConfig().serverRuntimeConfig
|
|
||||||
|
|
||||||
const sendFileAndDelete = async (name: string, path: string) => {
|
const sendFileAndDelete = async (name: string, path: string) => {
|
||||||
const stat = statSync(path)
|
const stat = statSync(path)
|
||||||
const stream = createReadStream(path)
|
const stream = createReadStream(path)
|
||||||
@ -16,13 +13,14 @@ const sendFileAndDelete = async (name: string, path: string) => {
|
|||||||
url: 'https://cloud.nicco.io/public.php/webdav/' + name,
|
url: 'https://cloud.nicco.io/public.php/webdav/' + name,
|
||||||
method: 'put',
|
method: 'put',
|
||||||
auth: {
|
auth: {
|
||||||
username: NEXTCLOUD_TOKEN,
|
username: process.env.NEXTCLOUD_TOKEN || '',
|
||||||
password: '',
|
password: '',
|
||||||
},
|
},
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Length': stat.size
|
'Content-Length': stat.size,
|
||||||
},
|
},
|
||||||
data: stream
|
data: stream,
|
||||||
|
maxContentLength: Infinity,
|
||||||
})
|
})
|
||||||
|
|
||||||
unlinkSync(path)
|
unlinkSync(path)
|
||||||
@ -30,23 +28,21 @@ const sendFileAndDelete = async (name: string, path: string) => {
|
|||||||
|
|
||||||
export default async (req: NextApiRequest, res: NextApiResponse) => {
|
export default async (req: NextApiRequest, res: NextApiResponse) => {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const form = new Formidable();
|
const form = new Formidable()
|
||||||
|
|
||||||
form.maxFileSize = 300 * 1024 * 1024 // 300MiB
|
form.maxFileSize = 300 * 1024 * 1024 // 300MiB
|
||||||
|
|
||||||
const body = await new Promise<any>((resolve, reject) => {
|
const body = await new Promise<any>((resolve, reject) => {
|
||||||
form.parse(req, (err: any, fields: any, files: any) => {
|
form.parse(req, (err: any, fields: any, files: any) => {
|
||||||
if (err) reject()
|
if (err) reject()
|
||||||
else resolve({
|
else
|
||||||
|
resolve({
|
||||||
fields: JSON.parse(fields.json),
|
fields: JSON.parse(fields.json),
|
||||||
files: Object.values(files)
|
files: Object.values(files),
|
||||||
|
})
|
||||||
})
|
})
|
||||||
});
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const { token, ...rest } = body.fields
|
const { token, ...rest } = body.fields
|
||||||
|
|
||||||
@ -54,7 +50,7 @@ export default async (req: NextApiRequest, res: NextApiResponse) => {
|
|||||||
url: 'https://www.google.com/recaptcha/api/siteverify',
|
url: 'https://www.google.com/recaptcha/api/siteverify',
|
||||||
method: 'post',
|
method: 'post',
|
||||||
params: {
|
params: {
|
||||||
secret: RECAPTCHA_SERVER,
|
secret: process.env.RECAPTCHA_SERVER,
|
||||||
response: token,
|
response: token,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@ -62,15 +58,13 @@ export default async (req: NextApiRequest, res: NextApiResponse) => {
|
|||||||
if (!data.success) throw new Error()
|
if (!data.success) throw new Error()
|
||||||
|
|
||||||
const now = Date.now()
|
const now = Date.now()
|
||||||
for (const file of body.files)
|
for (const file of body.files) await sendFileAndDelete(`${now}_${file.name}`, file.path)
|
||||||
sendFileAndDelete(`${now}_${file.name}`, file.path)
|
|
||||||
|
|
||||||
const txtFile = `${tmpdir()}/text`
|
const txtFile = `${tmpdir()}/text`
|
||||||
writeFileSync(txtFile, `${rest.contact}\n${rest.description}`)
|
writeFileSync(txtFile, `${rest.contact}\n${rest.description}`)
|
||||||
sendFileAndDelete(`${now}_details.txt`, txtFile)
|
await sendFileAndDelete(`${now}_details.txt`, txtFile)
|
||||||
|
|
||||||
res.status(200).end()
|
res.status(200).end()
|
||||||
|
|
||||||
} catch {
|
} catch {
|
||||||
res.status(400).end()
|
res.status(400).end()
|
||||||
}
|
}
|
||||||
|
@ -1,27 +1,37 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
const Home = () => {
|
const Home = () => {
|
||||||
return <div className='center'>
|
return (
|
||||||
|
<div className="center">
|
||||||
<h2>neodymium enabled waves. new.</h2>
|
<h2>neodymium enabled waves. new.</h2>
|
||||||
<p>
|
<p>
|
||||||
<b><u>neodymium:</u> </b>
|
<b>
|
||||||
|
<u>neodymium:</u>{' '}
|
||||||
|
</b>
|
||||||
<cite>
|
<cite>
|
||||||
a bright, silvery, reactive rare-earth element, found in monazite and bastnaesite and used for coloring glass, for doping laser glass and crystals, and in materials with strong, permanent magnetic properties that make them useful for computer and audio equipment. Atomic number 60; atomic weight 144.24; melting point 1,016°C; boiling point 3,074°C;
|
a bright, silvery, reactive rare-earth element, found in monazite and bastnaesite and used for coloring glass,
|
||||||
|
for doping laser glass and crystals, and in materials with strong, permanent magnetic properties that make
|
||||||
|
them useful for computer and audio equipment. Atomic number 60; atomic weight 144.24; melting point 1,016°C;
|
||||||
|
boiling point 3,074°C;
|
||||||
</cite>
|
</cite>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<b><u>enabled:</u> </b>
|
<b>
|
||||||
<cite>
|
<u>enabled:</u>{' '}
|
||||||
to make able; give power, means, competence, or ability to; authorize
|
</b>
|
||||||
</cite>
|
<cite>to make able; give power, means, competence, or ability to; authorize</cite>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<b><u>wave:</u> </b>
|
<b>
|
||||||
|
<u>wave:</u>{' '}
|
||||||
|
</b>
|
||||||
<cite>
|
<cite>
|
||||||
the continuous, repeating pattern in which some types of energy, such as sound, light, and heat, are spread or carried
|
the continuous, repeating pattern in which some types of energy, such as sound, light, and heat, are spread or
|
||||||
|
carried
|
||||||
</cite>
|
</cite>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Home
|
export default Home
|
@ -3,13 +3,13 @@ import React from 'react'
|
|||||||
import Form from '../screens/form'
|
import Form from '../screens/form'
|
||||||
|
|
||||||
const Home = () => {
|
const Home = () => {
|
||||||
return <div className='center color'>
|
return (
|
||||||
<h1 className='ma0'>mastering</h1>
|
<div className="center color">
|
||||||
|
<h1 className="ma0">mastering</h1>
|
||||||
<div>
|
<div>
|
||||||
<h3>how does it work?</h3>
|
<h3>how does it work?</h3>
|
||||||
<p>
|
<p>
|
||||||
My aim is to practise mastering.
|
My aim is to practise mastering. As anything it requires a lot of trial and error. So the idea is as follows:
|
||||||
As anything it requires a lot of trial and error. So the idea is as follows:
|
|
||||||
</p>
|
</p>
|
||||||
<ol>
|
<ol>
|
||||||
<li>You send me a track</li>
|
<li>You send me a track</li>
|
||||||
@ -18,19 +18,23 @@ const Home = () => {
|
|||||||
</ol>
|
</ol>
|
||||||
<h3>what's the catch then?</h3>
|
<h3>what's the catch then?</h3>
|
||||||
<p>
|
<p>
|
||||||
Nothing really. It's <b><i>completely free</i></b> for you.
|
Nothing really. It's{' '}
|
||||||
It goes without saying that of course I will not upload the tracks anywhere.
|
<b>
|
||||||
No surprises.
|
<i>completely free</i>
|
||||||
|
</b>{' '}
|
||||||
|
for you. It goes without saying that of course I will not upload the tracks anywhere. No surprises.
|
||||||
</p>
|
</p>
|
||||||
<h3>what if you like the result?</h3>
|
<h3>what if you like the result?</h3>
|
||||||
<p>
|
<p>
|
||||||
If you think the master is good you can use it for free withouth any royalties to me.
|
If you think the master is good you can use it for free withouth any royalties to me. It would be cool if you
|
||||||
It would be cool if you could reference me in the youtube description or spotify credits for mastering it, but it's totally optional and up to you.
|
could reference me in the youtube description or spotify credits for mastering it, but it's totally optional
|
||||||
|
and up to you.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<br />
|
<br />
|
||||||
<Form />
|
<Form />
|
||||||
</div>
|
</div>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Home
|
export default Home
|
@ -1,31 +1,56 @@
|
|||||||
import React, { useState, useEffect } from 'react'
|
import type { NextPage, GetStaticProps } from 'next'
|
||||||
import { NextPage } from 'next'
|
import React from 'react'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
|
|
||||||
const Home: NextPage = () => {
|
type Props = {
|
||||||
|
slugs: string[]
|
||||||
|
}
|
||||||
|
|
||||||
const [links, setLinks] = useState([] as string[])
|
const Home: NextPage<Props> = ({ slugs }) => {
|
||||||
|
const encoded = slugs.map((slug) => encodeURIComponent(`/fantus/${slug}/`))
|
||||||
|
|
||||||
useEffect(() => {
|
return (
|
||||||
axios.get('https://api.fantus.studio/directus/items/mixes?status=published')
|
<div className="sets">
|
||||||
.then(({ data }) => {
|
<h1 className="ma0">sets</h1>
|
||||||
setLinks(data.data.map((entry: any) => entry.link))
|
<p>collection of some sets made here and there.</p>
|
||||||
})
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return <div className='sets'>
|
|
||||||
<h1 className='ma0'>sets</h1>
|
|
||||||
<p>
|
|
||||||
collection of some sets made here and there.
|
|
||||||
</p>
|
|
||||||
<ul>
|
<ul>
|
||||||
{links.map(link => (
|
{encoded.map((link) => (
|
||||||
<li key={link}>
|
<li key={link}>
|
||||||
<iframe width="100%" height="120" src={link} />
|
<iframe
|
||||||
|
width="100%"
|
||||||
|
height="120"
|
||||||
|
src={`https://www.mixcloud.com/widget/iframe/?hide_cover=1&feed=${link}`}
|
||||||
|
/>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Home
|
export default Home
|
||||||
|
|
||||||
|
export const getStaticProps: GetStaticProps<Props> = async (context) => {
|
||||||
|
const { data } = await axios({
|
||||||
|
url: 'https://www.mixcloud.com/graphql',
|
||||||
|
method: 'post',
|
||||||
|
data: {
|
||||||
|
query: `
|
||||||
|
query UserUploadsQuery($lookup: UserLookup!, $orderBy: CloudcastOrderByEnum) {
|
||||||
|
user: userLookup(lookup: $lookup) {
|
||||||
|
uploads(first: 100, orderBy: $orderBy) {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
slug
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
variables: { lookup: { username: 'fantus' }, orderBy: 'LATEST' },
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const slugs: string[] = data.data.user.uploads.edges.map((item: any) => item.node.slug)
|
||||||
|
return { props: { slugs } }
|
||||||
|
}
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
const Home = () => {
|
const Home = () => {
|
||||||
return <div className='center'>
|
return (
|
||||||
|
<div className="center">
|
||||||
<h2>works</h2>
|
<h2>works</h2>
|
||||||
<p>
|
<p>in progress... lot of skratches lying around</p>
|
||||||
in progress... lot of skratches lying around
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Home
|
export default Home
|
@ -1,19 +1,11 @@
|
|||||||
import React, { useState, useRef, useCallback } from 'react'
|
import React, { useState, useCallback } from 'react'
|
||||||
import { useForm } from 'formhero'
|
import { useForm } from 'formhero'
|
||||||
import axios from 'axios'
|
|
||||||
import getConfig from 'next/config'
|
|
||||||
|
|
||||||
const { RECAPTCHA_CLIENT } = getConfig().publicRuntimeConfig
|
|
||||||
|
|
||||||
const initial = {
|
const initial = {
|
||||||
contact: '',
|
contact: '',
|
||||||
description: '',
|
description: '',
|
||||||
}
|
}
|
||||||
|
|
||||||
const ab2str = (buf: any): string => {
|
|
||||||
return String.fromCharCode.apply(null, buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
const Form: React.FC = () => {
|
const Form: React.FC = () => {
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
const [message, setMessage] = useState({
|
const [message, setMessage] = useState({
|
||||||
@ -24,11 +16,12 @@ const Form: React.FC = () => {
|
|||||||
const { field, form, setForm } = useForm(initial)
|
const { field, form, setForm } = useForm(initial)
|
||||||
const [files, setFiles] = useState([] as File[])
|
const [files, setFiles] = useState([] as File[])
|
||||||
|
|
||||||
const _onFileChange = useCallback(async (e: React.ChangeEvent<HTMLInputElement>) => {
|
const _onFileChange = useCallback(
|
||||||
|
async (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
setMessage({ title: '', error: false })
|
setMessage({ title: '', error: false })
|
||||||
const uploaded = Array.from(e.target.files || [])
|
const uploaded = Array.from(e.target.files || [])
|
||||||
|
|
||||||
const nonAudio = uploaded.find(file => !/^audio\//.test(file.type))
|
const nonAudio = uploaded.find((file) => !/^audio\//.test(file.type))
|
||||||
if (nonAudio) {
|
if (nonAudio) {
|
||||||
setMessage({
|
setMessage({
|
||||||
title: `Error: ${nonAudio.name} You can only select audio files.`,
|
title: `Error: ${nonAudio.name} You can only select audio files.`,
|
||||||
@ -38,13 +31,16 @@ const Form: React.FC = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setFiles([...files, ...uploaded])
|
setFiles([...files, ...uploaded])
|
||||||
}, [files])
|
},
|
||||||
|
[files]
|
||||||
|
)
|
||||||
|
|
||||||
const _clear = useCallback(() => {
|
const _clear = useCallback(() => {
|
||||||
setFiles([])
|
setFiles([])
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const _submit = useCallback((e: React.FormEvent) => {
|
const _submit = useCallback(
|
||||||
|
(e: React.FormEvent) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
|
||||||
if (loading) return
|
if (loading) return
|
||||||
@ -52,17 +48,21 @@ const Form: React.FC = () => {
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
window.grecaptcha.ready(() => {
|
window.grecaptcha.ready(() => {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
window.grecaptcha.execute(RECAPTCHA_CLIENT, { action: 'homepage' }).then(function (token) {
|
window.grecaptcha
|
||||||
|
.execute(process.env.NEXT_PUBLIC_RECAPTCHA_CLIENT, { action: 'homepage' })
|
||||||
|
.then(function (token: string) {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
setMessage({ title: '', error: false })
|
setMessage({ title: '', error: false })
|
||||||
|
|
||||||
|
|
||||||
const body = new FormData()
|
const body = new FormData()
|
||||||
|
|
||||||
body.append('json', JSON.stringify({
|
body.append(
|
||||||
|
'json',
|
||||||
|
JSON.stringify({
|
||||||
...form,
|
...form,
|
||||||
token,
|
token,
|
||||||
}))
|
})
|
||||||
|
)
|
||||||
|
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
body.append(file.name, file)
|
body.append(file.name, file)
|
||||||
@ -70,7 +70,7 @@ const Form: React.FC = () => {
|
|||||||
|
|
||||||
fetch('/api/form', {
|
fetch('/api/form', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body
|
body,
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
setForm(initial)
|
setForm(initial)
|
||||||
@ -81,57 +81,45 @@ const Form: React.FC = () => {
|
|||||||
.finally(() => setLoading(false))
|
.finally(() => setLoading(false))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}, [form, files, loading])
|
},
|
||||||
|
[form, files, loading]
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={_submit}>
|
<form onSubmit={_submit}>
|
||||||
<div className='body'>
|
<div className="body">
|
||||||
<h3 className='ma0 mb3'>submit track</h3>
|
<h3 className="ma0 mb3">submit track</h3>
|
||||||
<label className='text'>
|
<label className="text">
|
||||||
<small>contact email</small>
|
<small>contact email</small>
|
||||||
<input
|
<input type="email" disabled={loading} placeholder={'me@example.org'} {...field('contact')} />
|
||||||
type='email'
|
|
||||||
disabled={loading}
|
|
||||||
placeholder={'me@example.org'}
|
|
||||||
{...field('contact')}
|
|
||||||
/>
|
|
||||||
</label>
|
</label>
|
||||||
<br />
|
<br />
|
||||||
<label className='text'>
|
<label className="text">
|
||||||
<small>description</small>
|
<small>description</small>
|
||||||
<textarea
|
<textarea
|
||||||
rows={2}
|
rows={2}
|
||||||
placeholder='reference trakcs, comments, ...'
|
placeholder="reference trakcs, comments, ..."
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
{...field('description')}
|
{...field('description')}
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
<br />
|
<br />
|
||||||
<label className='file'>
|
<label className="file">
|
||||||
<input
|
<input type="file" multiple disabled={loading} onChange={_onFileChange} />
|
||||||
type='file'
|
|
||||||
multiple
|
|
||||||
disabled={loading}
|
|
||||||
onChange={_onFileChange}
|
|
||||||
/>
|
|
||||||
upload tracks [max. 300MiB]
|
upload tracks [max. 300MiB]
|
||||||
</label>
|
</label>
|
||||||
{files.length > 0 && (
|
{files.length > 0 && (
|
||||||
<div>
|
<div>
|
||||||
<input onClick={_clear} type='button' value='clear all' />
|
<input onClick={_clear} type="button" value="clear all" />
|
||||||
<ul>
|
<ul>
|
||||||
{files.map((file, i) => <li key={i}>
|
{files.map((file, i) => (
|
||||||
{file.name}
|
<li key={i}>{file.name}</li>
|
||||||
</li>)}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<br />
|
<br />
|
||||||
<input
|
<input type="submit" value={loading ? 'uploading...' : 'submit'} disabled={loading} />
|
||||||
type='submit'
|
|
||||||
value={loading ? 'uploading...' : 'submit'}
|
|
||||||
disabled={loading}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{!!message.title && (
|
{!!message.title && (
|
||||||
<div>
|
<div>
|
||||||
@ -141,7 +129,7 @@ const Form: React.FC = () => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='grc'>
|
<div className="grc">
|
||||||
This site is protected by reCAPTCHA and the Google
|
This site is protected by reCAPTCHA and the Google
|
||||||
<a href="https://policies.google.com/privacy">Privacy Policy</a> and
|
<a href="https://policies.google.com/privacy">Privacy Policy</a> and
|
||||||
<a href="https://policies.google.com/terms">Terms of Service</a> apply.
|
<a href="https://policies.google.com/terms">Terms of Service</a> apply.
|
||||||
|
@ -4,14 +4,15 @@ import Link from '../components/link'
|
|||||||
|
|
||||||
import '../styles/menu.styl'
|
import '../styles/menu.styl'
|
||||||
|
|
||||||
const HomeLink = () => <div className='home'>
|
const HomeLink = () => (
|
||||||
<Link href='/'>
|
<div className="home">
|
||||||
|
<Link href="/">
|
||||||
<div>fantus</div>
|
<div>fantus</div>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
const Menu: React.FC = () => {
|
const Menu: React.FC = () => {
|
||||||
|
|
||||||
const [open, setOpen] = useState(false)
|
const [open, setOpen] = useState(false)
|
||||||
|
|
||||||
const _close = useCallback(() => {
|
const _close = useCallback(() => {
|
||||||
@ -22,27 +23,29 @@ const Menu: React.FC = () => {
|
|||||||
setOpen(!open)
|
setOpen(!open)
|
||||||
}, [open])
|
}, [open])
|
||||||
|
|
||||||
return <nav className='main flex justify-between items-center'>
|
return (
|
||||||
|
<nav className="main flex justify-between items-center">
|
||||||
<HomeLink />
|
<HomeLink />
|
||||||
|
|
||||||
<img id='icon' src='/assets/menu.svg' onClick={_toggle} />
|
<img id="icon" src="/assets/menu.svg" onClick={_toggle} />
|
||||||
|
|
||||||
<div className={`links flex ${open ? 'open' : ''}`.trim()} onClick={_close}>
|
<div className={`links flex ${open ? 'open' : ''}`.trim()} onClick={_close}>
|
||||||
<HomeLink />
|
<HomeLink />
|
||||||
<Link href='/works'>
|
<Link href="/works">
|
||||||
<div>works</div>
|
<div>works</div>
|
||||||
</Link>
|
</Link>
|
||||||
<Link href='/sets'>
|
<Link href="/sets">
|
||||||
<div>sets</div>
|
<div>sets</div>
|
||||||
</Link>
|
</Link>
|
||||||
<Link href='/mastering'>
|
<Link href="/mastering">
|
||||||
<div>mastering</div>
|
<div>mastering</div>
|
||||||
</Link>
|
</Link>
|
||||||
<Link href='/about'>
|
<Link href="/about">
|
||||||
<div>about</div>
|
<div>about</div>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Menu
|
export default Menu
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "es2018",
|
"target": "es2019",
|
||||||
"module": "esnext",
|
"module": "esnext",
|
||||||
"jsx": "preserve",
|
"jsx": "preserve",
|
||||||
"strict": true,
|
"strict": true,
|
||||||
|
Loading…
Reference in New Issue
Block a user