mirror of https://github.com/gabehf/Koito.git
parent
7ff317756f
commit
3250a4ec3f
@ -1,14 +1,14 @@
|
||||
# v0.0.2
|
||||
# v0.0.3
|
||||
## Features
|
||||
- Configurable CORS policy via KOITO_CORS_ALLOWED_ORIGINS
|
||||
- A baseline mobile UI
|
||||
- Delete listens from the UI
|
||||
|
||||
## Enhancements
|
||||
- The import source is now saved as the client for the imported listen.
|
||||
- Better behaved mobile UI
|
||||
- Search now returns 8 items per category instead of 5
|
||||
|
||||
## Fixes
|
||||
- Account update form now works on enter key
|
||||
- Many mobile UI fixes
|
||||
|
||||
## Updates
|
||||
- Non-sensitive query parameters are logged with requests
|
||||
- Koito version number is embedded through tags
|
||||
- Refuses a config that changes the MusicBrainz rate limit while using the official MusicBrainz URL
|
||||
- Warns when enabling ListenBrainz relay with missing configuration
|
||||
@ -1,87 +0,0 @@
|
||||
# Welcome to React Router!
|
||||
|
||||
A modern, production-ready template for building full-stack React applications using React Router.
|
||||
|
||||
[](https://stackblitz.com/github/remix-run/react-router-templates/tree/main/default)
|
||||
|
||||
## Features
|
||||
|
||||
- 🚀 Server-side rendering
|
||||
- ⚡️ Hot Module Replacement (HMR)
|
||||
- 📦 Asset bundling and optimization
|
||||
- 🔄 Data loading and mutations
|
||||
- 🔒 TypeScript by default
|
||||
- 🎉 TailwindCSS for styling
|
||||
- 📖 [React Router docs](https://reactrouter.com/)
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Installation
|
||||
|
||||
Install the dependencies:
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
### Development
|
||||
|
||||
Start the development server with HMR:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Your application will be available at `http://localhost:5173`.
|
||||
|
||||
## Building for Production
|
||||
|
||||
Create a production build:
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
## Deployment
|
||||
|
||||
### Docker Deployment
|
||||
|
||||
To build and run using Docker:
|
||||
|
||||
```bash
|
||||
docker build -t my-app .
|
||||
|
||||
# Run the container
|
||||
docker run -p 3000:3000 my-app
|
||||
```
|
||||
|
||||
The containerized application can be deployed to any platform that supports Docker, including:
|
||||
|
||||
- AWS ECS
|
||||
- Google Cloud Run
|
||||
- Azure Container Apps
|
||||
- Digital Ocean App Platform
|
||||
- Fly.io
|
||||
- Railway
|
||||
|
||||
### DIY Deployment
|
||||
|
||||
If you're familiar with deploying Node applications, the built-in app server is production-ready.
|
||||
|
||||
Make sure to deploy the output of `npm run build`
|
||||
|
||||
```
|
||||
├── package.json
|
||||
├── package-lock.json (or pnpm-lock.yaml, or bun.lockb)
|
||||
├── build/
|
||||
│ ├── client/ # Static assets
|
||||
│ └── server/ # Server-side code
|
||||
```
|
||||
|
||||
## Styling
|
||||
|
||||
This template comes with [Tailwind CSS](https://tailwindcss.com/) already configured for a simple default starting experience. You can use whatever CSS framework you prefer.
|
||||
|
||||
---
|
||||
|
||||
Built with ❤️ using React Router.
|
||||
@ -1,48 +1,64 @@
|
||||
import React, { type PropsWithChildren, useState } from 'react';
|
||||
import React, { type PropsWithChildren, useEffect, useState } from 'react';
|
||||
|
||||
interface Props {
|
||||
inner: React.ReactNode
|
||||
position: string
|
||||
space: number
|
||||
extraClasses?: string
|
||||
hint?: string
|
||||
inner: React.ReactNode
|
||||
position: string
|
||||
space: number
|
||||
extraClasses?: string
|
||||
hint?: string
|
||||
}
|
||||
|
||||
export default function Popup({ inner, position, space, extraClasses, children }: PropsWithChildren<Props>) {
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
const [showPopup, setShowPopup] = useState(true);
|
||||
|
||||
let positionClasses
|
||||
let spaceCSS = {}
|
||||
if (position == "top") {
|
||||
positionClasses = `top-${space} -bottom-2 -translate-y-1/2 -translate-x-1/2`
|
||||
} else if (position == "right") {
|
||||
positionClasses = `bottom-1 -translate-x-1/2`
|
||||
spaceCSS = {left: 70 + space}
|
||||
useEffect(() => {
|
||||
const mediaQuery = window.matchMedia('(min-width: 640px)');
|
||||
|
||||
const handleChange = (e: MediaQueryListEvent) => {
|
||||
setShowPopup(e.matches);
|
||||
};
|
||||
|
||||
setShowPopup(mediaQuery.matches);
|
||||
|
||||
mediaQuery.addEventListener('change', handleChange);
|
||||
return () => mediaQuery.removeEventListener('change', handleChange);
|
||||
}, []);
|
||||
|
||||
let positionClasses = '';
|
||||
let spaceCSS: React.CSSProperties = {};
|
||||
if (position === 'top') {
|
||||
positionClasses = `top-${space} -bottom-2 -translate-y-1/2 -translate-x-1/2`;
|
||||
} else if (position === 'right') {
|
||||
positionClasses = `bottom-1 -translate-x-1/2`;
|
||||
spaceCSS = { left: 70 + space };
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className="relative"
|
||||
onMouseEnter={() => setIsVisible(true)}
|
||||
onMouseLeave={() => setIsVisible(false)}
|
||||
className="relative"
|
||||
onMouseEnter={() => setIsVisible(true)}
|
||||
onMouseLeave={() => setIsVisible(false)}
|
||||
>
|
||||
{children}
|
||||
<div
|
||||
className={`
|
||||
absolute
|
||||
${positionClasses}
|
||||
${extraClasses ? extraClasses : ''}
|
||||
bg-(--color-bg) color-fg border-1 border-(--color-bg-tertiary)
|
||||
px-3 py-2 rounded-lg
|
||||
transition-opacity duration-100
|
||||
${isVisible ? 'opacity-100' : 'opacity-0 pointer-events-none'}
|
||||
z-50 text-center
|
||||
flex
|
||||
`}
|
||||
style={spaceCSS}
|
||||
>
|
||||
{inner}
|
||||
</div>
|
||||
{children}
|
||||
{showPopup && (
|
||||
<div
|
||||
className={`
|
||||
absolute
|
||||
${positionClasses}
|
||||
${extraClasses ?? ''}
|
||||
bg-(--color-bg) color-fg border-1 border-(--color-bg-tertiary)
|
||||
px-3 py-2 rounded-lg
|
||||
transition-opacity duration-100
|
||||
${isVisible ? 'opacity-100' : 'opacity-0 pointer-events-none'}
|
||||
z-50 text-center
|
||||
flex
|
||||
`}
|
||||
style={spaceCSS}
|
||||
>
|
||||
{inner}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,43 +0,0 @@
|
||||
import { isRouteErrorResponse, Outlet } from "react-router";
|
||||
import Footer from "~/components/Footer";
|
||||
import type { Route } from "../+types/root";
|
||||
|
||||
export default function Root() {
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center mx-auto w-full">
|
||||
<Outlet />
|
||||
<Footer />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
|
||||
let message = "Oops!";
|
||||
let details = "An unexpected error occurred.";
|
||||
let stack: string | undefined;
|
||||
|
||||
if (isRouteErrorResponse(error)) {
|
||||
message = error.status === 404 ? "404" : "Error";
|
||||
details =
|
||||
error.status === 404
|
||||
? "The requested page could not be found."
|
||||
: error.statusText || details;
|
||||
} else if (import.meta.env.DEV && error && error instanceof Error) {
|
||||
details = error.message;
|
||||
stack = error.stack;
|
||||
}
|
||||
|
||||
return (
|
||||
<main className="pt-16 p-4 container mx-auto scroll-smooth">
|
||||
<h1>{message}</h1>
|
||||
<p>{details}</p>
|
||||
{stack && (
|
||||
<pre className="w-full p-4 overflow-x-auto">
|
||||
<code>{stack}</code>
|
||||
</pre>
|
||||
)}
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
Loading…
Reference in new issue