port webapp to vite
@@ -49,11 +49,6 @@ Generated\ Files/
|
||||
TestResult.xml
|
||||
nunit-*.xml
|
||||
|
||||
# Build Results of an ATL Project
|
||||
[Dd]ebugPS/
|
||||
[Rr]eleasePS/
|
||||
dlldata.c
|
||||
|
||||
# Benchmark Results
|
||||
BenchmarkDotNet.Artifacts/
|
||||
|
||||
@@ -62,9 +57,6 @@ project.lock.json
|
||||
project.fragment.lock.json
|
||||
artifacts/
|
||||
|
||||
# ASP.NET Scaffolding
|
||||
ScaffoldingReadMe.txt
|
||||
|
||||
# StyleCop
|
||||
StyleCopReport.xml
|
||||
|
||||
@@ -89,7 +81,6 @@ StyleCopReport.xml
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*_wpftmp.csproj
|
||||
*.log
|
||||
*.tlog
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
@@ -98,20 +89,6 @@ StyleCopReport.xml
|
||||
*.svclog
|
||||
*.scc
|
||||
|
||||
# Chutzpah Test files
|
||||
_Chutzpah*
|
||||
|
||||
# Visual C++ cache files
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opendb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.cachefile
|
||||
*.VC.db
|
||||
*.VC.VC.opendb
|
||||
|
||||
# Visual Studio profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
@@ -121,314 +98,50 @@ ipch/
|
||||
# Visual Studio Trace Files
|
||||
*.e2e
|
||||
|
||||
# TFS 2012 Local Workspace
|
||||
$tf/
|
||||
|
||||
# Guidance Automation Toolkit
|
||||
*.gpState
|
||||
|
||||
# ReSharper is a .NET coding add-in
|
||||
# ReSharper
|
||||
_ReSharper*/
|
||||
*.[Rr]e[Ss]harper
|
||||
*.DotSettings.user
|
||||
|
||||
# TeamCity is a build add-in
|
||||
_TeamCity*
|
||||
|
||||
# DotCover is a Code Coverage Tool
|
||||
*.dotCover
|
||||
|
||||
# AxoCover is a Code Coverage Tool
|
||||
.axoCover/*
|
||||
!.axoCover/settings.json
|
||||
|
||||
# Coverlet is a free, cross platform Code Coverage Tool
|
||||
coverage*.json
|
||||
coverage*.xml
|
||||
coverage*.info
|
||||
|
||||
# Visual Studio code coverage results
|
||||
*.coverage
|
||||
*.coveragexml
|
||||
|
||||
# NCrunch
|
||||
_NCrunch_*
|
||||
.*crunch*.local.xml
|
||||
nCrunchTemp_*
|
||||
|
||||
# MightyMoose
|
||||
*.mm.*
|
||||
AutoTest.Net/
|
||||
|
||||
# Web workbench (sass)
|
||||
.sass-cache/
|
||||
|
||||
# Installshield output folder
|
||||
[Ee]xpress/
|
||||
|
||||
# DocProject is a documentation generator add-in
|
||||
DocProject/buildhelp/
|
||||
DocProject/Help/*.HxT
|
||||
DocProject/Help/*.HxC
|
||||
DocProject/Help/*.hhc
|
||||
DocProject/Help/*.hhk
|
||||
DocProject/Help/*.hhp
|
||||
DocProject/Help/Html2
|
||||
DocProject/Help/html
|
||||
|
||||
# Click-Once directory
|
||||
publish/
|
||||
|
||||
# Publish Web Output
|
||||
*.[Pp]ublish.xml
|
||||
*.azurePubxml
|
||||
# Note: Comment the next line if you want to checkin your web deploy settings,
|
||||
# but database connection strings (with potential passwords) will be unencrypted
|
||||
*.pubxml
|
||||
*.publishproj
|
||||
|
||||
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||
# in these scripts will be unencrypted
|
||||
PublishScripts/
|
||||
|
||||
# NuGet Packages
|
||||
*.nupkg
|
||||
# NuGet Symbol Packages
|
||||
*.snupkg
|
||||
# The packages folder can be ignored because of Package Restore
|
||||
**/[Pp]ackages/*
|
||||
# except build/, which is used as an MSBuild target.
|
||||
!**/[Pp]ackages/build/
|
||||
# Uncomment if necessary however generally it will be regenerated when needed
|
||||
#!**/[Pp]ackages/repositories.config
|
||||
# NuGet v3's project.json files produces more ignorable files
|
||||
*.nuget.props
|
||||
*.nuget.targets
|
||||
|
||||
# Microsoft Azure Build Output
|
||||
csx/
|
||||
*.build.csdef
|
||||
|
||||
# Microsoft Azure Emulator
|
||||
ecf/
|
||||
rcf/
|
||||
|
||||
# Windows Store app package directories and files
|
||||
AppPackages/
|
||||
BundleArtifacts/
|
||||
Package.StoreAssociation.xml
|
||||
_pkginfo.txt
|
||||
*.appx
|
||||
*.appxbundle
|
||||
*.appxupload
|
||||
|
||||
# Visual Studio cache files
|
||||
# files ending in .cache can be ignored
|
||||
*.[Cc]ache
|
||||
# but keep track of directories ending in .cache
|
||||
!?*.[Cc]ache/
|
||||
|
||||
# Others
|
||||
ClientBin/
|
||||
~$*
|
||||
*~
|
||||
*.dbmdl
|
||||
*.dbproj.schemaview
|
||||
*.jfm
|
||||
*.pfx
|
||||
*.publishsettings
|
||||
orleans.codegen.cs
|
||||
|
||||
# Including strong name files can present a security risk
|
||||
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
||||
#*.snk
|
||||
|
||||
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||
#bower_components/
|
||||
|
||||
# RIA/Silverlight projects
|
||||
Generated_Code/
|
||||
|
||||
# Backup & report files from converting an old project file
|
||||
# to a newer Visual Studio version. Backup files are not needed,
|
||||
# because we have git ;-)
|
||||
_UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
UpgradeLog*.htm
|
||||
ServiceFabricBackup/
|
||||
*.rptproj.bak
|
||||
|
||||
# SQL Server files
|
||||
*.mdf
|
||||
*.ldf
|
||||
*.ndf
|
||||
|
||||
# Business Intelligence projects
|
||||
*.rdl.data
|
||||
*.bim.layout
|
||||
*.bim_*.settings
|
||||
*.rptproj.rsuser
|
||||
*- [Bb]ackup.rdl
|
||||
*- [Bb]ackup ([0-9]).rdl
|
||||
*- [Bb]ackup ([0-9][0-9]).rdl
|
||||
|
||||
# Microsoft Fakes
|
||||
FakesAssemblies/
|
||||
|
||||
# GhostDoc plugin setting file
|
||||
*.GhostDoc.xml
|
||||
|
||||
# Node.js Tools for Visual Studio
|
||||
.ntvs_analysis.dat
|
||||
node_modules/
|
||||
|
||||
# Visual Studio 6 build log
|
||||
*.plg
|
||||
|
||||
# Visual Studio 6 workspace options file
|
||||
*.opt
|
||||
|
||||
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||
*.vbw
|
||||
|
||||
# Visual Studio 6 auto-generated project file (contains which files were open etc.)
|
||||
*.vbp
|
||||
|
||||
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
|
||||
*.dsw
|
||||
*.dsp
|
||||
|
||||
# Visual Studio 6 technical files
|
||||
*.ncb
|
||||
*.aps
|
||||
|
||||
# Visual Studio LightSwitch build output
|
||||
**/*.HTMLClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/ModelManifest.xml
|
||||
**/*.Server/GeneratedArtifacts
|
||||
**/*.Server/ModelManifest.xml
|
||||
_Pvt_Extensions
|
||||
|
||||
# Paket dependency manager
|
||||
.paket/paket.exe
|
||||
paket-files/
|
||||
|
||||
# FAKE - F# Make
|
||||
.fake/
|
||||
|
||||
# CodeRush personal settings
|
||||
.cr/personal
|
||||
|
||||
# Python Tools for Visual Studio (PTVS)
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
# Cake - Uncomment if you are using it
|
||||
# tools/**
|
||||
# !tools/packages.config
|
||||
|
||||
# Tabs Studio
|
||||
*.tss
|
||||
|
||||
# Telerik's JustMock configuration file
|
||||
*.jmconfig
|
||||
|
||||
# BizTalk build output
|
||||
*.btp.cs
|
||||
*.btm.cs
|
||||
*.odx.cs
|
||||
*.xsd.cs
|
||||
|
||||
# OpenCover UI analysis results
|
||||
OpenCover/
|
||||
|
||||
# Azure Stream Analytics local run output
|
||||
ASALocalRun/
|
||||
|
||||
# MSBuild Binary and Structured Log
|
||||
*.binlog
|
||||
|
||||
# NVidia Nsight GPU debugger configuration file
|
||||
*.nvuser
|
||||
|
||||
# MFractors (Xamarin productivity tool) working folder
|
||||
.mfractor/
|
||||
|
||||
# Local History for Visual Studio
|
||||
.localhistory/
|
||||
|
||||
# Visual Studio History (VSHistory) files
|
||||
.vshistory/
|
||||
|
||||
# BeatPulse healthcheck temp database
|
||||
healthchecksdb
|
||||
|
||||
# Backup folder for Package Reference Convert tool in Visual Studio 2017
|
||||
MigrationBackup/
|
||||
|
||||
# Ionide (cross platform F# VS Code tools) working folder
|
||||
.ionide/
|
||||
|
||||
# Fody - auto-generated XML schema
|
||||
FodyWeavers.xsd
|
||||
|
||||
# VS Code files for those working on multiple tools
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
*.code-workspace
|
||||
|
||||
# Local History for Visual Studio Code
|
||||
.history/
|
||||
|
||||
# Windows Installer files from build outputs
|
||||
*.cab
|
||||
*.msi
|
||||
*.msix
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
# JetBrains Rider
|
||||
*.sln.iml
|
||||
|
||||
# dependencies
|
||||
node_modules
|
||||
.pnp
|
||||
.pnp.js
|
||||
.yarn/install-state.gz
|
||||
|
||||
# testing
|
||||
coverage
|
||||
|
||||
# next.js
|
||||
.next/
|
||||
out/
|
||||
|
||||
# production
|
||||
build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
# local env files
|
||||
.env*.local
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
Dockerfile
|
||||
.dockerignore
|
||||
node_modules
|
||||
npm-debug.log
|
||||
README.md
|
||||
.next
|
||||
!.next/static
|
||||
!.next/standalone
|
||||
.git
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"extends": "next/core-web-vitals"
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
FROM node:lts-alpine AS base
|
||||
|
||||
# Install dependencies only when needed
|
||||
FROM base AS deps
|
||||
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
|
||||
RUN apk add --no-cache libc6-compat
|
||||
WORKDIR /app
|
||||
|
||||
# Install dependencies based on the preferred package manager
|
||||
COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./
|
||||
RUN \
|
||||
if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
|
||||
elif [ -f package-lock.json ]; then npm ci; \
|
||||
elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm i --frozen-lockfile; \
|
||||
else echo "Lockfile not found." && exit 1; \
|
||||
fi
|
||||
|
||||
|
||||
# Rebuild the source code only when needed
|
||||
FROM base AS builder
|
||||
WORKDIR /app
|
||||
COPY --from=deps /app/node_modules ./node_modules
|
||||
COPY . .
|
||||
|
||||
ENV NEXT_TELEMETRY_DISABLED=1
|
||||
|
||||
RUN \
|
||||
if [ -f yarn.lock ]; then yarn run build; \
|
||||
elif [ -f package-lock.json ]; then npm run build; \
|
||||
elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm run build; \
|
||||
else echo "Lockfile not found." && exit 1; \
|
||||
fi
|
||||
|
||||
FROM base AS runner
|
||||
WORKDIR /app
|
||||
|
||||
ENV NODE_ENV=production
|
||||
ENV NEXT_TELEMETRY_DISABLED=1
|
||||
|
||||
RUN addgroup --system --gid 1001 nodejs
|
||||
RUN adduser --system --uid 1001 nextjs
|
||||
|
||||
COPY --from=builder /app/public ./public
|
||||
|
||||
# Set the correct permission for prerender cache
|
||||
RUN mkdir .next
|
||||
RUN chown nextjs:nodejs .next
|
||||
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
||||
|
||||
USER nextjs
|
||||
EXPOSE 3000
|
||||
ENV PORT=3000
|
||||
|
||||
CMD HOSTNAME="0.0.0.0" node server.js
|
||||
@@ -1,41 +0,0 @@
|
||||
import type {Metadata, Viewport} from "next";
|
||||
import {ThemeProvider} from "@/components/theme-provider";
|
||||
import "./globals.css";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "AimeNet",
|
||||
applicationName: "AimeNet",
|
||||
authors: [
|
||||
{
|
||||
name: "ppc",
|
||||
}
|
||||
],
|
||||
openGraph: {
|
||||
type: 'website',
|
||||
locale: 'en_US',
|
||||
siteName: 'AimeNet',
|
||||
description: 'Browser-local Aime/Amusement IC Card Switcher'
|
||||
}
|
||||
};
|
||||
|
||||
export const viewport: Viewport = {
|
||||
initialScale: 1,
|
||||
minimumScale: 1,
|
||||
maximumScale: 1,
|
||||
userScalable: false,
|
||||
width: 'device-width',
|
||||
themeColor: '#d2005b',
|
||||
}
|
||||
|
||||
export default function RootLayout({children}: Readonly<{ children: React.ReactNode; }>) {
|
||||
return (
|
||||
<html lang="en" suppressHydrationWarning>
|
||||
<head></head>
|
||||
<body className="min-h-screen bg-background">
|
||||
<ThemeProvider attribute="class" defaultTheme="system" enableSystem disableTransitionOnChange>
|
||||
{children}
|
||||
</ThemeProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
import {MetadataRoute} from 'next'
|
||||
|
||||
export default function manifest(): MetadataRoute.Manifest {
|
||||
return {
|
||||
id: '?pwa-home=1',
|
||||
name: 'AimeNet',
|
||||
description: 'Aime Card Switcher',
|
||||
start_url: '/',
|
||||
categories: ['utilities', 'games', 'entertainment'],
|
||||
orientation: 'portrait-primary',
|
||||
display: 'standalone',
|
||||
background_color: '#140606',
|
||||
theme_color: '#d2005b',
|
||||
icons: [
|
||||
{
|
||||
"src": "/icon2.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/icon3.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
shortcuts: [
|
||||
{
|
||||
url: '/',
|
||||
name: 'Servers',
|
||||
description: 'View, add or connect to servers'
|
||||
},
|
||||
{
|
||||
url: '/cards',
|
||||
name: 'Cards',
|
||||
description: 'Manage stored cards'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
import { MetadataRoute } from 'next'
|
||||
|
||||
export default function robots(): MetadataRoute.Robots {
|
||||
return {
|
||||
rules: {
|
||||
userAgent: '*',
|
||||
disallow: '/'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema.json",
|
||||
"style": "default",
|
||||
"rsc": true,
|
||||
"rsc": false,
|
||||
"tsx": true,
|
||||
"tailwind": {
|
||||
"config": "tailwind.config.ts",
|
||||
"css": "app/globals.css",
|
||||
"config": "tailwind.config.js",
|
||||
"css": "src/index.css",
|
||||
"baseColor": "slate",
|
||||
"cssVariables": true,
|
||||
"prefix": ""
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { ThemeProvider as NextThemesProvider } from "next-themes"
|
||||
import { type ThemeProviderProps } from "next-themes/dist/types"
|
||||
|
||||
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
|
||||
return <NextThemesProvider {...props}>{children}</NextThemesProvider>
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import js from '@eslint/js'
|
||||
import globals from 'globals'
|
||||
import reactHooks from 'eslint-plugin-react-hooks'
|
||||
import reactRefresh from 'eslint-plugin-react-refresh'
|
||||
import tseslint from 'typescript-eslint'
|
||||
|
||||
export default tseslint.config({
|
||||
extends: [js.configs.recommended, ...tseslint.configs.recommended],
|
||||
files: ['**/*.{ts,tsx}'],
|
||||
ignores: ['dist', 'src/components/ui'],
|
||||
languageOptions: {
|
||||
ecmaVersion: 2021,
|
||||
globals: globals.browser,
|
||||
},
|
||||
plugins: {
|
||||
'react-hooks': reactHooks,
|
||||
'react-refresh': reactRefresh,
|
||||
},
|
||||
rules: {
|
||||
...reactHooks.configs.recommended.rules,
|
||||
'react-refresh/only-export-components': [
|
||||
'warn',
|
||||
{allowConstantExport: true},
|
||||
],
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,40 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="theme-color" content="#d2005b">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no">
|
||||
|
||||
<title>AimeNet</title>
|
||||
|
||||
<meta name="author" content="ppc">
|
||||
<meta name="application-name" content="AimeNet">
|
||||
|
||||
<meta property="og:locale" content="en_US">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:title" content="AimeNet">
|
||||
<meta property="og:site_name" content="AimeNet">
|
||||
<meta property="og:description" content="Browser-local Aime/Amusement IC Card Switcher">
|
||||
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta name="twitter:title" content="AimeNet">
|
||||
<meta name="twitter:description" content="Browser-local Aime/Amusement IC Card Switcher">
|
||||
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-title" content="AimeNet">
|
||||
|
||||
<link rel="icon" href="/favicon.ico" type="image/x-icon" sizes="48x48">
|
||||
<link rel="icon" href="/icon-16.png" type="image/png" sizes="16x16">
|
||||
<link rel="icon" href="/icon-32.png" type="image/png" sizes="32x32">
|
||||
<link rel="icon" href="/icon-192.png" type="image/png" sizes="192x192">
|
||||
<link rel="icon" href="/icon-512.png" type="image/png" sizes="512x512">
|
||||
|
||||
<link rel="apple-touch-icon" href="/apple-icon.png" type="image/png" sizes="180x180">
|
||||
|
||||
<link rel="manifest" href="/app.webmanifest" crossorigin="use-credentials">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,6 +0,0 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
output: 'standalone',
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
@@ -1,35 +1,33 @@
|
||||
{
|
||||
"name": "aimenet-web-2",
|
||||
"version": "0.1.0",
|
||||
"name": "aimenet-web",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint"
|
||||
"dev": "vite",
|
||||
"build": "tsc -b && vite build",
|
||||
"lint": "eslint .",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hookform/resolvers": "^3.9.0",
|
||||
"@radix-ui/react-context-menu": "^2.2.1",
|
||||
"@radix-ui/react-dialog": "^1.1.1",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.1",
|
||||
"@radix-ui/react-label": "^2.1.0",
|
||||
"@radix-ui/react-separator": "^1.1.0",
|
||||
"@radix-ui/react-slot": "^1.1.0",
|
||||
"@radix-ui/react-toast": "^1.2.1",
|
||||
"@radix-ui/react-tooltip": "^1.1.2",
|
||||
"@uidotdev/usehooks": "^2.4.1",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.1",
|
||||
"detectincognitojs": "^1.3.5",
|
||||
"dexie": "^4.0.8",
|
||||
"dexie-react-hooks": "^1.1.7",
|
||||
"lucide-react": "^0.424.0",
|
||||
"next": "14.2.5",
|
||||
"next-themes": "^0.3.0",
|
||||
"react": "^18",
|
||||
"react-dom": "^18",
|
||||
"lucide-react": "^0.427.0",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-hook-form": "^7.52.2",
|
||||
"react-router-dom": "^6.26.0",
|
||||
"react-timeago": "^7.2.0",
|
||||
"tailwind-merge": "^2.4.0",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
@@ -37,14 +35,21 @@
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^18",
|
||||
"@types/react-dom": "^18",
|
||||
"@eslint/js": "^9.8.0",
|
||||
"@types/node": "^22.2.0",
|
||||
"@types/react": "^18.3.3",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@types/react-timeago": "^4.1.7",
|
||||
"eslint": "^8",
|
||||
"eslint-config-next": "14.2.5",
|
||||
"postcss": "^8",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"typescript": "^5"
|
||||
"@vitejs/plugin-react-swc": "^3.5.0",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"eslint": "^9.8.0",
|
||||
"eslint-plugin-react-hooks": "^5.1.0-rc.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.9",
|
||||
"globals": "^15.9.0",
|
||||
"postcss": "^8.4.41",
|
||||
"tailwindcss": "^3.4.9",
|
||||
"typescript": "^5.5.3",
|
||||
"typescript-eslint": "^8.0.0",
|
||||
"vite": "^5.4.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
/** @type {import('postcss-load-config').Config} */
|
||||
const config = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"id": "?pwa-home=1",
|
||||
"name": "AimeNet",
|
||||
"description": "Aime Card Switcher",
|
||||
"start_url": "/",
|
||||
"categories": [
|
||||
"utilities",
|
||||
"games",
|
||||
"entertainment"
|
||||
],
|
||||
"orientation": "portrait-primary",
|
||||
"display": "standalone",
|
||||
"background_color": "#140606",
|
||||
"theme_color": "#d2005b",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/icon-192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/icon-512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
"shortcuts": [
|
||||
{
|
||||
"url": "/",
|
||||
"name": "Servers",
|
||||
"description": "View, add or connect to servers"
|
||||
},
|
||||
{
|
||||
"url": "/cards",
|
||||
"name": "Cards",
|
||||
"description": "Manage stored cards"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 343 KiB After Width: | Height: | Size: 343 KiB |
@@ -0,0 +1,2 @@
|
||||
User-Agent: *
|
||||
Disallow: /
|
||||
@@ -1,8 +1,9 @@
|
||||
import {CreditCard, History} from "lucide-react";
|
||||
import {Card} from "@/components/ui/card";
|
||||
import React from "react";
|
||||
import {AimeCard} from "@/lib/database";
|
||||
import TimeAgo from "react-timeago";
|
||||
import {CreditCard, History} from "lucide-react";
|
||||
|
||||
import {AimeCard} from "@/lib/database";
|
||||
import {Card} from "@/components/ui/card";
|
||||
|
||||
export function AimeCardEntry(props: { card: AimeCard, children?: Readonly<React.ReactNode> }) {
|
||||
return (
|
||||
@@ -1,15 +1,24 @@
|
||||
"use client";
|
||||
|
||||
import {z} from "zod";
|
||||
import React from "react";
|
||||
import {Form, FormProvider, useForm} from "react-hook-form";
|
||||
import {FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage} from "@/components/ui/form";
|
||||
import {generateAimeCardId, validateAimeCardId} from "@/lib/aime-card";
|
||||
import {Check, X} from "lucide-react";
|
||||
import {FormProvider, useForm} from "react-hook-form";
|
||||
|
||||
import {useMediaQuery} from "@/hooks/use-media-query";
|
||||
import {zodResolver} from "@hookform/resolvers/zod";
|
||||
|
||||
import {AimeCard, db} from "@/lib/database";
|
||||
import {generateAimeCardId, validateAimeCardId} from "@/lib/aime-card";
|
||||
|
||||
import {Button} from "@/components/ui/button";
|
||||
import {Input} from "@/components/ui/input";
|
||||
import {AimeCard, db} from "@/lib/database";
|
||||
import {
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage
|
||||
} from "@/components/ui/form";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
@@ -18,14 +27,15 @@ import {
|
||||
DialogTrigger
|
||||
} from "@/components/ui/dialog";
|
||||
import {
|
||||
Drawer, DrawerClose,
|
||||
Drawer,
|
||||
DrawerClose,
|
||||
DrawerContent,
|
||||
DrawerDescription, DrawerFooter,
|
||||
DrawerDescription,
|
||||
DrawerFooter,
|
||||
DrawerHeader,
|
||||
DrawerTitle,
|
||||
DrawerTrigger
|
||||
} from "@/components/ui/drawer";
|
||||
import {Check, X} from "lucide-react";
|
||||
|
||||
const schema = z.object({
|
||||
name: z.string().min(1, {
|
||||
@@ -36,53 +46,6 @@ const schema = z.object({
|
||||
})
|
||||
});
|
||||
|
||||
export function CardCreationDialog(props: { children: Readonly<React.ReactNode> }) {
|
||||
const [open, setOpen] = React.useState(false);
|
||||
const isDesktop = useMediaQuery("(min-width: 768px)");
|
||||
|
||||
if (isDesktop) {
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogTrigger asChild>
|
||||
{props.children}
|
||||
</DialogTrigger>
|
||||
<DialogContent className="sm:max-w-[425px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Add new card</DialogTitle>
|
||||
</DialogHeader>
|
||||
<AimeCardCreationForm callback={() => setOpen(false)}/>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Drawer open={open} onOpenChange={setOpen}>
|
||||
<DrawerTrigger asChild>
|
||||
{props.children}
|
||||
</DrawerTrigger>
|
||||
<DrawerContent>
|
||||
<DrawerHeader className="text-left">
|
||||
<DrawerTitle>Add card</DrawerTitle>
|
||||
<DrawerDescription>
|
||||
Enter the card details below, and press submit to add it to your profile.
|
||||
</DrawerDescription>
|
||||
</DrawerHeader>
|
||||
<div className="px-4">
|
||||
<AimeCardCreationForm callback={() => setOpen(false)}/>
|
||||
</div>
|
||||
<DrawerFooter className="pt-2">
|
||||
<DrawerClose asChild>
|
||||
<Button variant="outline">
|
||||
<X className="w-4 h-4 mr-2"/> Cancel
|
||||
</Button>
|
||||
</DrawerClose>
|
||||
</DrawerFooter>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
);
|
||||
}
|
||||
|
||||
function AimeCardCreationForm(props: { callback?: () => void }) {
|
||||
const form = useForm<z.infer<typeof schema>>({
|
||||
resolver: zodResolver(schema),
|
||||
@@ -150,4 +113,51 @@ function AimeCardCreationForm(props: { callback?: () => void }) {
|
||||
</form>
|
||||
</FormProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export function CardCreationDialog(props: { children: Readonly<React.ReactNode> }) {
|
||||
const [open, setOpen] = React.useState(false);
|
||||
const isDesktop = useMediaQuery("(min-width: 768px)");
|
||||
|
||||
if (isDesktop) {
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogTrigger asChild>
|
||||
{props.children}
|
||||
</DialogTrigger>
|
||||
<DialogContent className="sm:max-w-[425px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Add new card</DialogTitle>
|
||||
</DialogHeader>
|
||||
<AimeCardCreationForm callback={() => setOpen(false)}/>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Drawer open={open} onOpenChange={setOpen}>
|
||||
<DrawerTrigger asChild>
|
||||
{props.children}
|
||||
</DrawerTrigger>
|
||||
<DrawerContent>
|
||||
<DrawerHeader className="text-left">
|
||||
<DrawerTitle>Add card</DrawerTitle>
|
||||
<DrawerDescription>
|
||||
Enter the card details below, and press submit to add it to your profile.
|
||||
</DrawerDescription>
|
||||
</DrawerHeader>
|
||||
<div className="px-4">
|
||||
<AimeCardCreationForm callback={() => setOpen(false)}/>
|
||||
</div>
|
||||
<DrawerFooter className="pt-2">
|
||||
<DrawerClose asChild>
|
||||
<Button variant="outline">
|
||||
<X className="w-4 h-4 mr-2"/> Cancel
|
||||
</Button>
|
||||
</DrawerClose>
|
||||
</DrawerFooter>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import {ServerOff} from "lucide-react";
|
||||
import {useEffect, useState} from "react";
|
||||
import detectIncognito from "detectincognitojs";
|
||||
import React, {useEffect, useState} from "react";
|
||||
import {Alert, AlertDescription, AlertTitle} from "@/components/ui/alert";
|
||||
|
||||
export function IncognitoStorageWarning() {
|
||||
@@ -1,5 +1,4 @@
|
||||
import {Loader} from "lucide-react";
|
||||
import React from "react";
|
||||
|
||||
export function LoadingIndicator() {
|
||||
return (
|
||||
@@ -1,5 +1,4 @@
|
||||
import {CreditCard} from "lucide-react";
|
||||
import React from "react";
|
||||
|
||||
export function NoCardsNotification() {
|
||||
return (<div className="flex flex-col gap-2 items-center text-gray-500 pt-6 pb-5">
|
||||
@@ -0,0 +1,73 @@
|
||||
import {createContext, useContext, useEffect, useState} from "react"
|
||||
|
||||
type Theme = "dark" | "light" | "system"
|
||||
|
||||
type ThemeProviderProps = {
|
||||
children: React.ReactNode
|
||||
defaultTheme?: Theme
|
||||
storageKey?: string
|
||||
}
|
||||
|
||||
type ThemeProviderState = {
|
||||
theme: Theme
|
||||
setTheme: (theme: Theme) => void
|
||||
}
|
||||
|
||||
const initialState: ThemeProviderState = {
|
||||
theme: "system",
|
||||
setTheme: () => null,
|
||||
}
|
||||
|
||||
const ThemeProviderContext = createContext<ThemeProviderState>(initialState)
|
||||
|
||||
export function ThemeProvider({
|
||||
children,
|
||||
defaultTheme = "system",
|
||||
storageKey = "vite-ui-theme",
|
||||
...props
|
||||
}: ThemeProviderProps) {
|
||||
const [theme, setTheme] = useState<Theme>(
|
||||
() => (localStorage.getItem(storageKey) as Theme) || defaultTheme
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
const root = window.document.documentElement
|
||||
|
||||
root.classList.remove("light", "dark")
|
||||
|
||||
if (theme === "system") {
|
||||
const systemTheme = window.matchMedia("(prefers-color-scheme: dark)")
|
||||
.matches
|
||||
? "dark"
|
||||
: "light"
|
||||
|
||||
root.classList.add(systemTheme)
|
||||
return
|
||||
}
|
||||
|
||||
root.classList.add(theme)
|
||||
}, [theme])
|
||||
|
||||
const value = {
|
||||
theme,
|
||||
setTheme: (theme: Theme) => {
|
||||
localStorage.setItem(storageKey, theme)
|
||||
setTheme(theme)
|
||||
},
|
||||
}
|
||||
|
||||
return (
|
||||
<ThemeProviderContext.Provider {...props} value={value}>
|
||||
{children}
|
||||
</ThemeProviderContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export const useTheme = () => {
|
||||
const context = useContext(ThemeProviderContext)
|
||||
|
||||
if (context === undefined)
|
||||
throw new Error("useTheme must be used within a ThemeProvider")
|
||||
|
||||
return context
|
||||
}
|
||||
@@ -9,8 +9,7 @@ const alertVariants = cva(
|
||||
variants: {
|
||||
variant: {
|
||||
default: "bg-background text-foreground",
|
||||
destructive:
|
||||
"border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
|
||||
destructive: "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
|
||||
blank: "[&>svg]:text-inherit"
|
||||
},
|
||||
},
|
||||
@@ -1,5 +1,3 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as DialogPrimitive from "@radix-ui/react-dialog"
|
||||
import { X } from "lucide-react"
|
||||
@@ -1,5 +1,3 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { Drawer as DrawerPrimitive } from "vaul"
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as TooltipPrimitive from "@radix-ui/react-tooltip"
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import './index.css'
|
||||
|
||||
import {StrictMode} from 'react'
|
||||
import {createRoot} from 'react-dom/client'
|
||||
import {createBrowserRouter, RouterProvider} from "react-router-dom"
|
||||
|
||||
import {Toaster} from "@/components/ui/toaster.tsx";
|
||||
import {ThemeProvider} from "@/components/theme-provider.tsx";
|
||||
|
||||
import CardManager from "@/pages/card-manager.tsx"
|
||||
import ServerCardIn from "@/pages/card-in-screen.tsx"
|
||||
import ServerListing from "@/pages/server-listing.tsx"
|
||||
|
||||
const router = createBrowserRouter([
|
||||
{
|
||||
path: '/',
|
||||
element: <ServerListing/>
|
||||
},
|
||||
{
|
||||
path: '/cards',
|
||||
element: <CardManager/>
|
||||
},
|
||||
{
|
||||
path: '/servers/:serverId',
|
||||
element: <ServerCardIn/>
|
||||
}
|
||||
])
|
||||
|
||||
createRoot(document.getElementById('root')!).render(
|
||||
<StrictMode>
|
||||
<ThemeProvider defaultTheme="system" storageKey="vite-ui-theme">
|
||||
<RouterProvider router={router}/>
|
||||
|
||||
<Toaster/>
|
||||
</ThemeProvider>
|
||||
</StrictMode>,
|
||||
)
|
||||
@@ -1,57 +1,24 @@
|
||||
"use client";
|
||||
|
||||
import {useEffect, useState} from "react";
|
||||
import {useNavigate, useParams} from "react-router-dom";
|
||||
import {useLiveQuery} from "dexie-react-hooks";
|
||||
import React, {useEffect, useState} from "react";
|
||||
import {useParams, useRouter} from "next/navigation";
|
||||
import {ChevronRight, Circle, CircleCheckBig, Home, Pencil, Plus, ServerOff, Wallet} from "lucide-react";
|
||||
import {ChevronRight, Circle, CircleCheckBig, Home, Plus, ServerOff, Wallet} from "lucide-react";
|
||||
|
||||
import {AimeCard, AimeServer, db} from "@/lib/database";
|
||||
import {getServerInfo, serverCardIn} from "@/lib/aime-net";
|
||||
import {Button} from "@/components/ui/button";
|
||||
|
||||
import {Footer} from "@/components/footer";
|
||||
import {Toaster} from "@/components/ui/toaster";
|
||||
import {Button} from "@/components/ui/button";
|
||||
import {useToast} from "@/components/ui/use-toast";
|
||||
import {Separator} from "@/components/ui/separator";
|
||||
import {Alert, AlertDescription, AlertTitle} from "@/components/ui/alert";
|
||||
import {Tooltip, TooltipContent, TooltipProvider, TooltipTrigger} from "@/components/ui/tooltip";
|
||||
|
||||
import {AimeCardEntry} from "@/components/aime-card";
|
||||
import {LoadingIndicator} from "@/components/loading-indicator";
|
||||
import {CardCreationDialog} from "@/components/card-creation-form";
|
||||
import {NoCardsNotification} from "@/components/no-cards-notification";
|
||||
import {Alert, AlertDescription, AlertTitle} from "@/components/ui/alert";
|
||||
import {Tooltip, TooltipContent, TooltipProvider, TooltipTrigger} from "@/components/ui/tooltip";
|
||||
|
||||
function CardList(props: { cards: ReadonlyArray<AimeCard> | null, cardSelected: (card: AimeCard) => void }) {
|
||||
const router = useRouter();
|
||||
|
||||
return (<>
|
||||
<h4 className="text-2xl font-bold">{props.cards?.length ? "Cards" : "Additional Cards"}</h4>
|
||||
<div className="flex gap-2">
|
||||
<CardCreationDialog>
|
||||
<Button className="bg-blue-500 flex-grow">
|
||||
<Plus className="mr-2 h-4 w-4"/> Add Card
|
||||
</Button>
|
||||
</CardCreationDialog>
|
||||
|
||||
<Button onClick={() => router.push('/cards')} variant="outline" size="icon">
|
||||
<Wallet className="h-4 w-4"/>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{props.cards?.length ? props.cards.map(x => <AimeCardEntry card={x} key={x.id}>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button size="icon" variant="ghost" onClick={() => props.cardSelected(x)}>
|
||||
<Circle className="w-5 h-5"/>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="left">
|
||||
<p>Select Card</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</AimeCardEntry>) : <NoCardsNotification/>}
|
||||
</>);
|
||||
}
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
async function performCardInAction(card: AimeCard, server: AimeServer, setCardInStatus: (status: boolean) => void, toaster: any) {
|
||||
setCardInStatus(true);
|
||||
|
||||
@@ -76,12 +43,47 @@ async function performCardInAction(card: AimeCard, server: AimeServer, setCardIn
|
||||
setTimeout(() => setCardInStatus(false), 1000);
|
||||
}
|
||||
}
|
||||
/* eslint-enable @typescript-eslint/no-explicit-any */
|
||||
|
||||
export default function ServerPage() {
|
||||
const {serverId} = useParams<{ serverId: string }>();
|
||||
function CardList(props: { cards: ReadonlyArray<AimeCard> | null, cardSelected: (card: AimeCard) => void }) {
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (<>
|
||||
<h4 className="text-2xl font-bold">{props.cards?.length ? "Cards" : "Additional Cards"}</h4>
|
||||
<div className="flex gap-2">
|
||||
<CardCreationDialog>
|
||||
<Button className="bg-blue-500 flex-grow">
|
||||
<Plus className="mr-2 h-4 w-4"/> Add Card
|
||||
</Button>
|
||||
</CardCreationDialog>
|
||||
|
||||
<Button onClick={() => navigate('/cards')} variant="outline" size="icon">
|
||||
<Wallet className="h-4 w-4"/>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{props.cards?.length ? props.cards.map(x => <AimeCardEntry card={x} key={x.id}>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button size="icon" variant="ghost" onClick={() => props.cardSelected(x)}>
|
||||
<Circle className="w-5 h-5"/>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="left">
|
||||
<p>Select Card</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</AimeCardEntry>) : <NoCardsNotification/>}
|
||||
</>);
|
||||
}
|
||||
|
||||
function ServerCardInPage() {
|
||||
const {toast} = useToast();
|
||||
const router = useRouter();
|
||||
const {serverId} = useParams<{ serverId: string }>();
|
||||
|
||||
const navigate = useNavigate();
|
||||
const cards = useLiveQuery(() => db.cards.toArray());
|
||||
|
||||
const [server, setServer] = useState<AimeServer | null>(null);
|
||||
@@ -93,15 +95,20 @@ export default function ServerPage() {
|
||||
useEffect(() => setSelectedCard(cards?.sort(x => x.lastUsed)[0]), [cards]);
|
||||
useEffect(() => {
|
||||
const loadServer = async () => {
|
||||
if (!serverId) {
|
||||
navigate('/');
|
||||
return;
|
||||
}
|
||||
|
||||
const sid = parseInt(serverId);
|
||||
if (isNaN(sid)) {
|
||||
router.push('/');
|
||||
navigate('/');
|
||||
return;
|
||||
}
|
||||
|
||||
const server = await db.servers.get(sid);
|
||||
if (!server) {
|
||||
router.push('/');
|
||||
navigate('/');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -128,7 +135,7 @@ export default function ServerPage() {
|
||||
<main className="flex flex-col gap-5 container min-h-screen py-10">
|
||||
<div className="flex items-center gap-1">
|
||||
<h2 className="text-4xl font-bold select-none flex-grow">{server?.name ?? "Server"}</h2>
|
||||
<Button variant="ghost" onClick={() => router.push('/')}>
|
||||
<Button variant="ghost" onClick={() => navigate('/')}>
|
||||
<Home className="h-4 w-4 sm:mr-2"/>
|
||||
<span className="hidden sm:block">Servers</span>
|
||||
</Button>
|
||||
@@ -177,8 +184,8 @@ export default function ServerPage() {
|
||||
)}
|
||||
<Footer/>
|
||||
</main>
|
||||
|
||||
<Toaster/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ServerCardInPage;
|
||||
@@ -1,22 +1,22 @@
|
||||
"use client";
|
||||
import {useNavigate} from "react-router-dom";
|
||||
import {useLiveQuery} from "dexie-react-hooks";
|
||||
import {Plus, Server, Trash} from "lucide-react";
|
||||
|
||||
import {AimeCard, db} from "@/lib/database";
|
||||
|
||||
import React from "react";
|
||||
import {Separator} from "@/components/ui/separator";
|
||||
import {Footer} from "@/components/footer";
|
||||
import {Button} from "@/components/ui/button";
|
||||
import {Plus, Server, Trash} from "lucide-react";
|
||||
import {useRouter} from "next/navigation";
|
||||
import {useLiveQuery} from "dexie-react-hooks";
|
||||
import {AimeCard, db} from "@/lib/database";
|
||||
import {LoadingIndicator} from "@/components/loading-indicator";
|
||||
import {CardCreationDialog} from "@/components/card-creation-form";
|
||||
import {Separator} from "@/components/ui/separator";
|
||||
import {
|
||||
ContextMenu,
|
||||
ContextMenuContent,
|
||||
ContextMenuItem,
|
||||
ContextMenuTrigger
|
||||
} from "@/components/ui/context-menu";
|
||||
|
||||
import {AimeCardEntry} from "@/components/aime-card";
|
||||
import {LoadingIndicator} from "@/components/loading-indicator";
|
||||
import {CardCreationDialog} from "@/components/card-creation-form";
|
||||
import {IncognitoStorageWarning} from "@/components/incognito-warning";
|
||||
import {NoCardsNotification} from "@/components/no-cards-notification";
|
||||
|
||||
@@ -56,15 +56,15 @@ function CardList(props: { cards: AimeCard[] | undefined }) {
|
||||
</>);
|
||||
}
|
||||
|
||||
export default function CardManagerPage() {
|
||||
const router = useRouter();
|
||||
function CardManagerPage() {
|
||||
const navigate = useNavigate();
|
||||
const cards = useLiveQuery(() => db.cards.toArray());
|
||||
|
||||
return (
|
||||
<main className="flex flex-col gap-5 container min-h-screen py-10">
|
||||
<div className="flex items-center gap-1">
|
||||
<h2 className="text-4xl font-bold select-none flex-grow">Cards</h2>
|
||||
<Button variant="ghost" size={"default"} onClick={() => router.push('/')}>
|
||||
<Button variant="ghost" size={"default"} onClick={() => navigate('/')}>
|
||||
<Server className="h-4 w-4 sm:mr-2"/>
|
||||
<span className="hidden sm:block">Manage Servers</span>
|
||||
</Button>
|
||||
@@ -78,4 +78,6 @@ export default function CardManagerPage() {
|
||||
<Footer/>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default CardManagerPage;
|
||||
@@ -1,12 +1,10 @@
|
||||
"use client";
|
||||
|
||||
import {z} from "zod";
|
||||
import Link from "next/link";
|
||||
import React, {useState} from "react";
|
||||
import TimeAgo from "react-timeago";
|
||||
import React, {useState} from "react";
|
||||
import {useForm} from "react-hook-form";
|
||||
import {useRouter} from "next/navigation";
|
||||
import {AppRouterInstance} from "next/dist/shared/lib/app-router-context.shared-runtime";
|
||||
import {useNavigate} from 'react-router-dom';
|
||||
import {useLiveQuery} from "dexie-react-hooks";
|
||||
import {zodResolver} from "@hookform/resolvers/zod";
|
||||
import {
|
||||
ChevronRight,
|
||||
Earth,
|
||||
@@ -16,14 +14,17 @@ import {
|
||||
SquareArrowOutUpRight,
|
||||
Trash, WalletCards
|
||||
} from "lucide-react";
|
||||
import {useLiveQuery} from "dexie-react-hooks";
|
||||
|
||||
import {AimeServer, db} from "@/lib/database";
|
||||
import {zodResolver} from "@hookform/resolvers/zod";
|
||||
|
||||
import {Card} from "@/components/ui/card";
|
||||
import {Footer} from "@/components/footer";
|
||||
import {Input} from "@/components/ui/input";
|
||||
import {Button} from "@/components/ui/button";
|
||||
import {Footer} from "@/components/footer";
|
||||
import {Separator} from "@/components/ui/separator";
|
||||
import {LoadingIndicator} from "@/components/loading-indicator";
|
||||
import {Alert, AlertDescription, AlertTitle} from "@/components/ui/alert";
|
||||
import {Form, FormControl, FormField, FormItem, FormMessage} from "@/components/ui/form";
|
||||
import {
|
||||
ContextMenu,
|
||||
ContextMenuContent,
|
||||
@@ -31,20 +32,13 @@ import {
|
||||
ContextMenuSeparator,
|
||||
ContextMenuTrigger
|
||||
} from "@/components/ui/context-menu";
|
||||
import {Alert, AlertDescription, AlertTitle} from "@/components/ui/alert";
|
||||
import {Form, FormControl, FormField, FormItem, FormMessage} from "@/components/ui/form";
|
||||
import {LoadingIndicator} from "@/components/loading-indicator";
|
||||
|
||||
const serverAdditionSchema = z.object({
|
||||
address: z.string().startsWith("http")
|
||||
});
|
||||
|
||||
function redirectToServerPage(router: AppRouterInstance, server: AimeServer) {
|
||||
router.push(`/servers/${server.id}`);
|
||||
}
|
||||
|
||||
function ServerList(props: { servers: AimeServer[] | undefined }) {
|
||||
const router = useRouter();
|
||||
const navigate = useNavigate();
|
||||
|
||||
if (!props.servers) {
|
||||
return (<LoadingIndicator/>);
|
||||
@@ -83,14 +77,14 @@ function ServerList(props: { servers: AimeServer[] | undefined }) {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Button variant="ghost" size="icon" onClick={() => redirectToServerPage(router, s)}>
|
||||
<Button variant="ghost" size="icon" onClick={() => navigate(`/servers/${s.id}`)}>
|
||||
<ChevronRight className="h-4 w-4"/>
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
</ContextMenuTrigger>
|
||||
<ContextMenuContent>
|
||||
<ContextMenuItem inset onClick={() => redirectToServerPage(router, s)}>
|
||||
<ContextMenuItem inset onClick={() => navigate(`/servers/${s.id}`)}>
|
||||
Select Server
|
||||
</ContextMenuItem>
|
||||
<ContextMenuSeparator/>
|
||||
@@ -105,7 +99,7 @@ function ServerList(props: { servers: AimeServer[] | undefined }) {
|
||||
}
|
||||
|
||||
function ServerAddForm() {
|
||||
const router = useRouter();
|
||||
const navigate = useNavigate();
|
||||
const form = useForm<z.infer<typeof serverAdditionSchema>>({
|
||||
resolver: zodResolver(serverAdditionSchema),
|
||||
defaultValues: {
|
||||
@@ -136,9 +130,9 @@ function ServerAddForm() {
|
||||
name: `Server #${newId}`
|
||||
});
|
||||
|
||||
router.push(`/servers/${newId}`);
|
||||
navigate(`/servers/${newId}`);
|
||||
} else {
|
||||
router.push(`/servers/${existingServer.id}`);
|
||||
navigate(`/servers/${existingServer.id}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,11 +159,12 @@ function ServerAddForm() {
|
||||
<AlertTitle>HTTPS Warning</AlertTitle>
|
||||
<AlertDescription>
|
||||
AimeNet doesn't support HTTPS out-the-box.
|
||||
If you're using HTTPS, ensure a reverse-proxy or <Link target="_blank"
|
||||
className="underline"
|
||||
href="https://learn.microsoft.com/en-us/aspnet/core/fundamentals/servers/httpsys">
|
||||
HTTP.SYS <SquareArrowOutUpRight className="inline h-3 w-3"/>
|
||||
</Link> has been configured and a valid, trusted SSL certificate has been configured.
|
||||
If you're using HTTPS, ensure a reverse-proxy or
|
||||
<a target="_blank"
|
||||
className="underline"
|
||||
href="https://learn.microsoft.com/en-us/aspnet/core/fundamentals/servers/httpsys">
|
||||
HTTP.SYS <SquareArrowOutUpRight className="inline h-3 w-3"/>
|
||||
</a> has been configured and a valid, trusted SSL certificate has been configured.
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
</>
|
||||
@@ -201,15 +196,15 @@ function ServerAddForm() {
|
||||
);
|
||||
}
|
||||
|
||||
export default function ServerListingPage() {
|
||||
function ServerListing() {
|
||||
const navigate = useNavigate();
|
||||
const servers = useLiveQuery(() => db.servers.toArray());
|
||||
const router = useRouter();
|
||||
|
||||
return (
|
||||
<main className="flex flex-col gap-5 container min-h-screen py-10">
|
||||
<div className="flex items-center gap-1">
|
||||
<h2 className="text-4xl font-bold select-none flex-grow">Servers</h2>
|
||||
<Button variant="ghost" size={"default"} onClick={() => router.push('/cards')}>
|
||||
<Button variant="ghost" size={"default"} onClick={() => navigate('/cards')}>
|
||||
<WalletCards className="h-4 w-4 sm:mr-2"/>
|
||||
<span className="hidden sm:block">Manage Cards</span>
|
||||
</Button>
|
||||
@@ -224,3 +219,5 @@ export default function ServerListingPage() {
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
export default ServerListing;
|
||||
@@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
||||
@@ -0,0 +1,74 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
darkMode: ["class"],
|
||||
content: [
|
||||
'./src/**/*.{ts,tsx}',
|
||||
],
|
||||
prefix: "",
|
||||
theme: {
|
||||
container: {
|
||||
center: true,
|
||||
padding: "2rem",
|
||||
screens: {
|
||||
"2xl": "1400px",
|
||||
},
|
||||
},
|
||||
extend: {
|
||||
colors: {
|
||||
border: "hsl(var(--border))",
|
||||
input: "hsl(var(--input))",
|
||||
ring: "hsl(var(--ring))",
|
||||
background: "hsl(var(--background))",
|
||||
foreground: "hsl(var(--foreground))",
|
||||
primary: {
|
||||
DEFAULT: "hsl(var(--primary))",
|
||||
foreground: "hsl(var(--primary-foreground))",
|
||||
},
|
||||
secondary: {
|
||||
DEFAULT: "hsl(var(--secondary))",
|
||||
foreground: "hsl(var(--secondary-foreground))",
|
||||
},
|
||||
destructive: {
|
||||
DEFAULT: "hsl(var(--destructive))",
|
||||
foreground: "hsl(var(--destructive-foreground))",
|
||||
},
|
||||
muted: {
|
||||
DEFAULT: "hsl(var(--muted))",
|
||||
foreground: "hsl(var(--muted-foreground))",
|
||||
},
|
||||
accent: {
|
||||
DEFAULT: "hsl(var(--accent))",
|
||||
foreground: "hsl(var(--accent-foreground))",
|
||||
},
|
||||
popover: {
|
||||
DEFAULT: "hsl(var(--popover))",
|
||||
foreground: "hsl(var(--popover-foreground))",
|
||||
},
|
||||
card: {
|
||||
DEFAULT: "hsl(var(--card))",
|
||||
foreground: "hsl(var(--card-foreground))",
|
||||
},
|
||||
},
|
||||
borderRadius: {
|
||||
lg: "var(--radius)",
|
||||
md: "calc(var(--radius) - 2px)",
|
||||
sm: "calc(var(--radius) - 4px)",
|
||||
},
|
||||
keyframes: {
|
||||
"accordion-down": {
|
||||
from: { height: "0" },
|
||||
to: { height: "var(--radix-accordion-content-height)" },
|
||||
},
|
||||
"accordion-up": {
|
||||
from: { height: "var(--radix-accordion-content-height)" },
|
||||
to: { height: "0" },
|
||||
},
|
||||
},
|
||||
animation: {
|
||||
"accordion-down": "accordion-down 0.2s ease-out",
|
||||
"accordion-up": "accordion-up 0.2s ease-out",
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [require("tailwindcss-animate")],
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
import type {Config} from "tailwindcss"
|
||||
import {fontFamily} from "tailwindcss/defaultTheme"
|
||||
|
||||
const config = {
|
||||
darkMode: ["class"],
|
||||
content: [
|
||||
'**/*.{ts,tsx}',
|
||||
],
|
||||
prefix: "",
|
||||
theme: {
|
||||
container: {
|
||||
center: true,
|
||||
padding: "2rem",
|
||||
screens: {
|
||||
"2xl": "1400px",
|
||||
},
|
||||
},
|
||||
extend: {
|
||||
fontFamily: {
|
||||
sans: ["system-ui"],
|
||||
},
|
||||
colors: {
|
||||
border: "hsl(var(--border))",
|
||||
input: "hsl(var(--input))",
|
||||
ring: "hsl(var(--ring))",
|
||||
background: "hsl(var(--background))",
|
||||
foreground: "hsl(var(--foreground))",
|
||||
primary: {
|
||||
DEFAULT: "hsl(var(--primary))",
|
||||
foreground: "hsl(var(--primary-foreground))",
|
||||
},
|
||||
secondary: {
|
||||
DEFAULT: "hsl(var(--secondary))",
|
||||
foreground: "hsl(var(--secondary-foreground))",
|
||||
},
|
||||
destructive: {
|
||||
DEFAULT: "hsl(var(--destructive))",
|
||||
foreground: "hsl(var(--destructive-foreground))",
|
||||
},
|
||||
muted: {
|
||||
DEFAULT: "hsl(var(--muted))",
|
||||
foreground: "hsl(var(--muted-foreground))",
|
||||
},
|
||||
accent: {
|
||||
DEFAULT: "hsl(var(--accent))",
|
||||
foreground: "hsl(var(--accent-foreground))",
|
||||
},
|
||||
popover: {
|
||||
DEFAULT: "hsl(var(--popover))",
|
||||
foreground: "hsl(var(--popover-foreground))",
|
||||
},
|
||||
card: {
|
||||
DEFAULT: "hsl(var(--card))",
|
||||
foreground: "hsl(var(--card-foreground))",
|
||||
},
|
||||
},
|
||||
borderRadius: {
|
||||
lg: "var(--radius)",
|
||||
md: "calc(var(--radius) - 2px)",
|
||||
sm: "calc(var(--radius) - 4px)",
|
||||
},
|
||||
keyframes: {
|
||||
"accordion-down": {
|
||||
from: {height: "0"},
|
||||
to: {height: "var(--radix-accordion-content-height)"},
|
||||
},
|
||||
"accordion-up": {
|
||||
from: {height: "var(--radix-accordion-content-height)"},
|
||||
to: {height: "0"},
|
||||
},
|
||||
},
|
||||
animation: {
|
||||
"accordion-down": "accordion-down 0.2s ease-out",
|
||||
"accordion-up": "accordion-up 0.2s ease-out",
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [require("tailwindcss-animate")],
|
||||
} satisfies Config
|
||||
|
||||
export default config
|
||||
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2021",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": [
|
||||
"ES2021",
|
||||
"DOM",
|
||||
"DOM.Iterable"
|
||||
],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"./src/*"
|
||||
]
|
||||
},
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"isolatedModules": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
]
|
||||
}
|
||||
@@ -1,26 +1,19 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"@/*": ["./*"]
|
||||
"files": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.app.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.node.json"
|
||||
}
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
],
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"./src/*"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"lib": ["ES2023"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"isolatedModules": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import path from 'path';
|
||||
import {defineConfig} from 'vite'
|
||||
import react from '@vitejs/plugin-react-swc'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": path.resolve(__dirname, "./src"),
|
||||
},
|
||||
},
|
||||
})
|
||||