Compare commits

...

7 commits

Author SHA1 Message Date
ae22351367 Give RequestsTable a minimum height
Some checks failed
/ build (push) Has been cancelled
2025-06-19 19:56:32 -06:00
d3439321ba Implement requests page 2025-06-19 19:42:24 -06:00
75eaf17346 Development environment update 2025-06-19 18:27:39 -06:00
3a9f558672 Update localization setup 2025-05-18 11:57:15 -06:00
a5fde1b6c8 Add data migration to npm scripts 2025-05-10 00:36:07 -06:00
5c4f46a41e Add DefaultSEO to all pages 2025-05-10 00:23:21 -06:00
042758d27c Migrate donator requests to limit of 5 2025-05-10 00:02:16 -06:00
33 changed files with 10521 additions and 7561 deletions

8
.editorconfig Normal file
View file

@ -0,0 +1,8 @@
root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 2
indent_style = space
insert_final_newline = true

4
.gitattributes vendored Normal file
View file

@ -0,0 +1,4 @@
/.yarn/** linguist-vendored
/.yarn/releases/* binary
/.yarn/plugins/**/* binary
/.pnp.* binary linguist-generated

7
.gitignore vendored
View file

@ -6,6 +6,13 @@ dist/
# dependencies # dependencies
node_modules/ node_modules/
.yarn/*
!.yarn/cache
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions
# logs # logs
npm-debug.log* npm-debug.log*

22
.vscode/launch.json vendored
View file

@ -2,10 +2,28 @@
"version": "0.2.0", "version": "0.2.0",
"configurations": [ "configurations": [
{ {
"command": "yarn dev", "command": "yarn astro dev",
"name": "Development server", "name": "Astro",
"request": "launch",
"type": "node-terminal",
"serverReadyAction": {
"pattern": "http://localhost:([0-9]+)/",
"uriFormat": "http://localhost:%s",
"action": "openExternally"
}
},
{
"command": "yarn prisma generate --sql --watch",
"name": "Prisma",
"request": "launch", "request": "launch",
"type": "node-terminal" "type": "node-terminal"
} }
],
"compounds": [
{
"name": "Deveopment server",
"configurations": ["Astro", "Prisma"],
"stopAll": true
}
] ]
} }

1
.yarnrc.yml Normal file
View file

@ -0,0 +1 @@
nodeLinker: node-modules

View file

@ -3,10 +3,10 @@ import { defineConfig, envField } from 'astro/config'
import tailwindcss from '@tailwindcss/vite' import tailwindcss from '@tailwindcss/vite'
import node from '@astrojs/node' import node from '@astrojs/node'
import react from '@astrojs/react' import react from '@astrojs/react'
import paraglide from '@inlang/paraglide-astro' import { paraglideVitePlugin } from '@inlang/paraglide-js'
import icon from 'astro-icon' import icon from 'astro-icon'
import { languageTags } from './project.inlang/settings.json' import { locales } from './project.inlang/settings.json'
// https://astro.build/config // https://astro.build/config
export default defineConfig({ export default defineConfig({
@ -31,19 +31,23 @@ export default defineConfig({
}, },
site: 'https://sittingonclouds.net', site: 'https://sittingonclouds.net',
i18n: { i18n: {
locales: languageTags, locales,
defaultLocale: 'en', defaultLocale: 'en',
routing: { routing: {
prefixDefaultLocale: false, prefixDefaultLocale: false,
redirectToDefaultLocale: true redirectToDefaultLocale: true
} }
}, },
integrations: [ integrations: [icon({ iconDir: 'src/img/icons' }), react()],
paraglide({ project: './project.inlang', outdir: './src/paraglide' }), vite: {
icon({ iconDir: 'src/img/icons' }), plugins: [
react() tailwindcss(),
], paraglideVitePlugin({
vite: { plugins: [tailwindcss()] }, project: './project.inlang',
outdir: './src/paraglide'
})
]
},
image: { domains: ['cdn.sittingonclouds.net'] }, image: { domains: ['cdn.sittingonclouds.net'] },
output: 'server', output: 'server',
adapter: node({ mode: 'standalone' }), adapter: node({ mode: 'standalone' }),
@ -51,7 +55,7 @@ export default defineConfig({
'/en/[...params]': '/[...params]', '/en/[...params]': '/[...params]',
'/profile': { status: 307, destination: '/maintenance' }, '/profile': { status: 307, destination: '/maintenance' },
'/profile/[username]': { status: 307, destination: '/maintenance' }, '/profile/[username]': { status: 307, destination: '/maintenance' },
'/request': { status: 307, destination: '/maintenance' } '/request': { status: 308, destination: '/requests' }
}, },
security: { security: {
checkOrigin: false checkOrigin: false

View file

@ -90,5 +90,10 @@
"variousGames": "Various Games", "variousGames": "Various Games",
"searchResultsFor": "Search results for", "searchResultsFor": "Search results for",
"firstResults": "Showing first {take} results", "firstResults": "Showing first {take} results",
"moreResults": "Showing {start}-{end} results" "moreResults": "Showing {start}-{end} results",
"requestID": "Request ID",
"userID": "userID",
"state": "State",
"createdAt": "Created At",
"updatedAt": "Updated At"
} }

View file

@ -3,21 +3,22 @@
"type": "module", "type": "module",
"version": "1.0.0", "version": "1.0.0",
"scripts": { "scripts": {
"start": "npm run dev", "dev": "astro dev",
"dev": "prisma generate --sql && astro dev", "build": "yarn run prisma:build && astro build",
"build": "prisma generate --sql && npm run paraglide:compile && astro build", "prisma:migrate": "prisma migrate deploy && tsx ./prisma/migrate.ts",
"preview": "astro preview", "prisma:build": "yarn prisma:migrate && prisma generate --sql"
"paraglide:compile": "paraglide-js compile --project ./project.inlang --outdir ./src\\paraglide"
}, },
"dependencies": { "dependencies": {
"@ag-grid-community/locale": "^33.3.2",
"@astrojs/node": "9.0.0", "@astrojs/node": "9.0.0",
"@astrojs/react": "4.1.3", "@astrojs/react": "4.1.3",
"@astrojs/rss": "4.0.11", "@astrojs/rss": "4.0.11",
"@inlang/paraglide-astro": "^0.2.2",
"@prisma/client": "^6.4.1", "@prisma/client": "^6.4.1",
"@tailwindcss/vite": "^4.0.7", "@tailwindcss/vite": "^4.0.7",
"@types/react": "^18.3.12", "@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1", "@types/react-dom": "^18.3.1",
"ag-grid-community": "^33.3.1",
"ag-grid-react": "^33.3.1",
"astro": "^5.3.0", "astro": "^5.3.0",
"astro-icon": "^1.1.1", "astro-icon": "^1.1.1",
"astro-seo": "^0.8.4", "astro-seo": "^0.8.4",
@ -36,11 +37,12 @@
"slugify": "^1.6.6", "slugify": "^1.6.6",
"superstruct": "^2.0.2", "superstruct": "^2.0.2",
"tailwindcss": "^4.0.7", "tailwindcss": "^4.0.7",
"tsx": "^4.19.4",
"typescript": "^5.6.2", "typescript": "^5.6.2",
"use-immer": "^0.11.0" "use-immer": "^0.11.0"
}, },
"devDependencies": { "devDependencies": {
"@inlang/paraglide-js": "1.11.2", "@inlang/paraglide-js": "^2.0.12",
"@parcel/watcher": "^2.4.1", "@parcel/watcher": "^2.4.1",
"@types/nodemailer": "^6.4.17", "@types/nodemailer": "^6.4.17",
"concurrently": "^8.2.2", "concurrently": "^8.2.2",
@ -52,5 +54,5 @@
"prettier": "^3.3.3", "prettier": "^3.3.3",
"prettier-config-standard": "^7.0.0" "prettier-config-standard": "^7.0.0"
}, },
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" "packageManager": "yarn@4.9.1"
} }

View file

@ -0,0 +1,36 @@
import { RequestState, type Prisma } from '@prisma/client'
import prismaClient from 'utils/prisma-client'
const LIMIT_PENDING = 5
export default async function MigrationFn(tx: Prisma.TransactionClient) {
const donatorRequests = await tx.requests.findMany({
where: { donator: true, state: RequestState.PENDING }
})
const donatorMap = new Map<string, typeof donatorRequests>()
donatorRequests.forEach((request) => {
const userID = request.userID as string
donatorMap.set(userID, [...(donatorMap.get(userID) || []), request])
})
const holdRequests = new Set<number>()
for (const requests of donatorMap.values()) {
if (requests.length <= LIMIT_PENDING) continue
const end = requests.length - LIMIT_PENDING
const sortedRequests = requests.sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime()).slice(0, end)
sortedRequests.forEach((r) => {
holdRequests.add(r.id)
})
}
await prismaClient.requests.updateMany({
where: { id: { in: Array.from(holdRequests) } },
data: { state: RequestState.HOLD }
})
console.log(`Updated ${holdRequests.size} requests to HOLD state`)
}

37
prisma/migrate.ts Normal file
View file

@ -0,0 +1,37 @@
import { Prisma } from '@prisma/client'
import fs from 'fs'
import path from 'path'
import { createRequire } from 'module'
import prismaClient from 'utils/prisma-client'
const require = createRequire(import.meta.url)
export type MigrationFn = (tx: Prisma.TransactionClient) => Promise<void>
interface Migration {
id: string
migrationFn: MigrationFn
}
const dataMigrationsPath = path.join(import.meta.dirname, 'data_migrations')
const migrationFiles = fs.readdirSync(dataMigrationsPath).filter((file) => file.endsWith('.ts'))
const migrations: Migration[] = migrationFiles
.sort()
.map((id) => ({ id, migrationFn: require(path.join(dataMigrationsPath, id)).default }))
.filter((migration) => migration.migrationFn !== undefined)
if (migrations.length === 0) console.log('No data migrations to run.')
for (const { id, migrationFn } of migrations) {
const startDate = new Date()
const migration = await prismaClient.migration.findFirst({ where: { id } })
if (migration === null) {
console.log(`Migrating ${id}`)
await prismaClient.$transaction(async (tx) => {
await migrationFn(tx)
await tx.migration.create({ data: { id } })
})
const endDate = new Date()
console.log(`Migrated ${id} in ${(endDate.getTime() - startDate.getTime()) / 1000}s`)
}
}

View file

@ -0,0 +1,7 @@
-- CreateTable
CREATE TABLE `Migration` (
`id` VARCHAR(191) NOT NULL,
`createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

View file

@ -0,0 +1,8 @@
/*
Warnings:
- You are about to alter the column `state` on the `requests` table. The data in that column could be lost. The data in that column will be cast from `VarChar(255)` to `Enum(EnumId(1))`.
*/
-- AlterTable
ALTER TABLE `requests` MODIFY `state` ENUM('complete', 'hold', 'pending') NOT NULL;

View file

@ -0,0 +1,19 @@
/*
Warnings:
- You are about to drop the column `user` on the `requests` table. All the data in the column will be lost.
- You are about to drop the column `createdAt` on the `roles` table. All the data in the column will be lost.
- You are about to drop the column `updatedAt` on the `roles` table. All the data in the column will be lost.
- Made the column `userID` on table `requests` required. This step will fail if there are existing NULL values in that column.
*/
DELETE FROM `requests` WHERE `userId` IS NULL;
-- AlterTable
ALTER TABLE `requests` DROP COLUMN `user`,
MODIFY `userID` VARCHAR(255) NOT NULL;
-- AlterTable
ALTER TABLE `roles` DROP COLUMN `createdAt`,
DROP COLUMN `updatedAt`;

View file

@ -1,3 +1,3 @@
# Please do not edit this file manually # Please do not edit this file manually
# It should be added in your version-control system (e.g., Git) # It should be added in your version-control system (e.g., Git)
provider = "mysql" provider = "mysql"

View file

@ -334,13 +334,18 @@ model related_album {
@@id([albumId, relatedId]) @@id([albumId, relatedId])
} }
enum RequestState {
COMPLETE @map("complete")
HOLD @map("hold")
PENDING @map("pending")
}
model requests { model requests {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
title String? @db.VarChar(255) title String? @db.VarChar(255)
link String? @db.VarChar(255) link String? @db.VarChar(255)
user String? @db.VarChar(255) userID String @db.VarChar(255)
userID String? @db.VarChar(255) state RequestState
state String @db.VarChar(255)
donator Boolean donator Boolean
reason String? @db.VarChar(255) reason String? @db.VarChar(255)
comments String? @db.VarChar(255) comments String? @db.VarChar(255)
@ -353,8 +358,6 @@ model requests {
model roles { model roles {
name String @id @db.VarChar(255) name String @id @db.VarChar(255)
permissions Json? permissions Json?
createdAt DateTime @db.DateTime(0)
updatedAt DateTime @db.DateTime(0)
users User_Role[] users User_Role[]
} }
@ -402,7 +405,7 @@ model submissions {
model users { model users {
id String @id @db.VarChar(255) id String @id @db.VarChar(255)
name String @db.VarChar(20) name String @db.VarChar(20)
username String? @unique @db.VarChar(255) username String? @unique @db.VarChar(255)
displayUsername String @default("Default display name") displayUsername String @default("Default display name")
email String? @unique @db.VarChar(255) email String? @unique @db.VarChar(255)
emailVerified Boolean emailVerified Boolean
@ -461,3 +464,8 @@ model account {
user users @relation(fields: [userId], references: [id]) user users @relation(fields: [userId], references: [id])
} }
model Migration {
id String @id
createdAt DateTime @default(now())
}

View file

@ -1 +1 @@
021513eea48bfa4e8387b34adaf7bdc8f8f2c6a7b8fa2a20bc5f8a2a424f5295 52m6ykHaIPT1QnSo04

View file

@ -1,17 +1,12 @@
{ {
"$schema": "https://inlang.com/schema/project-settings", "$schema": "https://inlang.com/schema/project-settings",
"sourceLanguageTag": "en", "baseLocale": "en",
"languageTags": [ "locales": ["en"],
"en"
],
"modules": [ "modules": [
"https://cdn.jsdelivr.net/npm/@inlang/message-lint-rule-empty-pattern@latest/dist/index.js", "https://cdn.jsdelivr.net/npm/@inlang/plugin-message-format@4/dist/index.js",
"https://cdn.jsdelivr.net/npm/@inlang/message-lint-rule-missing-translation@latest/dist/index.js", "https://cdn.jsdelivr.net/npm/@inlang/plugin-m-function-matcher@2/dist/index.js"
"https://cdn.jsdelivr.net/npm/@inlang/message-lint-rule-without-source@latest/dist/index.js",
"https://cdn.jsdelivr.net/npm/@inlang/plugin-message-format@latest/dist/index.js",
"https://cdn.jsdelivr.net/npm/@inlang/plugin-m-function-matcher@latest/dist/index.js"
], ],
"plugin.inlang.messageFormat": { "plugin.inlang.messageFormat": {
"pathPattern": "./messages/{languageTag}.json" "pathPattern": "./messages/{locale}.json"
} }
} }

View file

@ -0,0 +1,45 @@
import {
ModuleRegistry,
ColumnAutoSizeModule,
ColumnHoverModule,
TextFilterModule,
NumberFilterModule,
DateFilterModule,
QuickFilterModule,
TextEditorModule,
LargeTextEditorModule,
SelectEditorModule,
NumberEditorModule,
DateEditorModule,
CheckboxEditorModule,
LocaleModule,
ClientSideRowModelModule,
colorSchemeDark,
themeQuartz
} from 'ag-grid-community'
ModuleRegistry.registerModules([
ColumnAutoSizeModule,
ColumnHoverModule,
TextFilterModule,
NumberFilterModule,
DateFilterModule,
QuickFilterModule,
TextEditorModule,
LargeTextEditorModule,
SelectEditorModule,
NumberEditorModule,
DateEditorModule,
CheckboxEditorModule,
LocaleModule,
ClientSideRowModelModule
])
const AgGridTheme = themeQuartz.withPart(colorSchemeDark)
export default AgGridTheme
import { AG_GRID_LOCALE_EN } from '@ag-grid-community/locale'
export const AgGridLocales = {
en: { AG_GRID_LOCALE_EN }
}

View file

@ -54,7 +54,6 @@ const { session } = Astro.locals
<a href='/'><NavButton>{m.home()}</NavButton></a> <a href='/'><NavButton>{m.home()}</NavButton></a>
<a href='/last-added'><NavButton>{m.lastaddednav()}</NavButton></a> <a href='/last-added'><NavButton>{m.lastaddednav()}</NavButton></a>
<a href='/album/list'><NavButton>{m.albumlist()}</NavButton></a> <a href='/album/list'><NavButton>{m.albumlist()}</NavButton></a>
<Dropdown> <Dropdown>
{m.games()} {m.games()}
<Fragment slot='items'> <Fragment slot='items'>
@ -65,7 +64,6 @@ const { session } = Astro.locals
<DropdownItem href='/platform/list'>{m.platforms()}</DropdownItem> <DropdownItem href='/platform/list'>{m.platforms()}</DropdownItem>
</Fragment> </Fragment>
</Dropdown> </Dropdown>
<Dropdown> <Dropdown>
{m.animation()} {m.animation()}
<Fragment slot='items'> <Fragment slot='items'>
@ -74,13 +72,13 @@ const { session } = Astro.locals
<DropdownItem href='/studio/list'>{m.studios()}</DropdownItem> <DropdownItem href='/studio/list'>{m.studios()}</DropdownItem>
</Fragment> </Fragment>
</Dropdown> </Dropdown>
<a href='/requests'>
<NavButton>{m.requests()}</NavButton>
</a>
{ {
session ? ( session ? (
<> <>
<a href='/requests'>
<NavButton>{m.requests()}</NavButton>
</a>
<NavButton>{m.submitalbum()}</NavButton> <NavButton>{m.submitalbum()}</NavButton>
<Dropdown> <Dropdown>
{m.adminGrounds()} {m.adminGrounds()}

View file

@ -0,0 +1,36 @@
import { type ColDef, type GridOptions } from 'ag-grid-community'
import { AgGridReact } from 'ag-grid-react'
import type { Prisma } from '@prisma/client'
import { m } from 'paraglide/messages.js'
import AgGridTheme from 'components/AgGrid/AgGridTheme'
const gridOptions: GridOptions = {
ensureDomOrder: true,
enableCellTextSelection: true
}
const colDefs: ColDef[] = [
{ field: 'id', headerName: m.requestID(), filter: true },
{ field: 'title', headerName: m.requests(), filter: true },
{ field: 'userID', headerName: m.userID(), filter: true },
{ field: 'state', headerName: m.state(), filter: true },
{ field: 'createdAt', headerName: m.createdAt() },
{ field: 'updatedAt', headerName: m.updatedAt() }
]
export default function RequestsTable(props: { initial: Prisma.requestsGetPayload<{}>[] }) {
const { initial } = props
return (
<div className='w-full min-h-[500px] px-4 py-4'>
<AgGridReact
gridOptions={gridOptions}
rowData={initial}
columnDefs={colDefs}
theme={AgGridTheme}
autoSizeStrategy={{ type: 'fitCellContents' }}
/>
</div>
)
}

View file

@ -1,14 +1,13 @@
--- ---
import { Toaster } from 'react-hot-toast' import { Toaster } from 'react-hot-toast'
import { languageTag } from '../paraglide/runtime'
import Header from 'components/Header.astro' import Header from 'components/Header.astro'
import Footer from 'components/Footer.astro' import Footer from 'components/Footer.astro'
import 'styles/global.css' import 'styles/global.css'
--- ---
<html lang={languageTag()} dir={Astro.locals.paraglide.dir} class='h-full'> <html class='h-full'>
<head> <head>
<link rel='preconnect' href='https://fonts.googleapis.com' /> <link rel='preconnect' href='https://fonts.googleapis.com' />
<link rel='preconnect' href='https://fonts.gstatic.com' crossorigin /> <link rel='preconnect' href='https://fonts.gstatic.com' crossorigin />

View file

@ -1,5 +1,6 @@
import { auth } from 'auth' import { auth } from 'auth'
import { defineMiddleware } from 'astro:middleware' import { defineMiddleware } from 'astro:middleware'
import { paraglideMiddleware } from 'paraglide/server'
import PAGES from 'utils/pages.json' import PAGES from 'utils/pages.json'
import prismaClient from 'utils/prisma-client' import prismaClient from 'utils/prisma-client'
@ -30,5 +31,5 @@ export const onRequest = defineMiddleware(async (context, next) => {
context.locals.pages = [] context.locals.pages = []
} }
return next() return paraglideMiddleware(context.request, () => next())
}) })

View file

@ -1,9 +1,11 @@
--- ---
export const prerender = true export const prerender = true
import DefaultSEO from 'components/DefaultSEO.astro'
import BaseLayout from '../layouts/base.astro' import BaseLayout from '../layouts/base.astro'
--- ---
<DefaultSEO />
<BaseLayout> <BaseLayout>
<div class='flex flex-1 flex-col bg-soc-green-dark py-6'> <div class='flex flex-1 flex-col bg-soc-green-dark py-6'>
<div class='text-md` text-center'> <div class='text-md` text-center'>

View file

@ -3,10 +3,12 @@ export const prerender = true
import { Image } from 'astro:assets' import { Image } from 'astro:assets'
import BaseLayout from '../layouts/base.astro' import BaseLayout from '../layouts/base.astro'
import DefaultSEO from 'components/DefaultSEO.astro'
import GuraGif from '../img/assets/doggo-thumbs-up.gif' import GuraGif from '../img/assets/doggo-thumbs-up.gif'
--- ---
<DefaultSEO />
<BaseLayout> <BaseLayout>
<div class='flex flex-1 flex-col bg-soc-green-dark py-6'> <div class='flex flex-1 flex-col bg-soc-green-dark py-6'>
<div class='text-md` text-center'>Something went wrong.... yubi yubi</div> <div class='text-md` text-center'>Something went wrong.... yubi yubi</div>

View file

@ -0,0 +1,27 @@
import type { APIRoute } from 'astro'
import * as s from 'superstruct'
import prismaClient from 'utils/prisma-client'
import { Status, parseForm } from 'utils/form'
import { EditRequest } from 'schemas/requests'
export const POST: APIRoute = async ({ request, locals }) => {
const { permissions, user } = locals
if (!user || !permissions.includes('REQUESTS')) return Status(403)
let body
try {
const formData = await parseForm(await request.formData())
body = s.create(formData, EditRequest)
} catch (err) {
return Status(422, (err as Error).message)
}
try {
await prismaClient.requests.update({ where: { id: body.id }, data: { ...body, updatedAt: new Date() } })
return Status(200, body.id.toString())
} catch (err) {
console.error(err)
return Status(500, (err as Error).message)
}
}

View file

@ -4,15 +4,17 @@ import * as m from 'paraglide/messages'
import { getRandom } from 'utils/form' import { getRandom } from 'utils/form'
import { getRandomAlbum } from 'utils/queries' import { getRandomAlbum } from 'utils/queries'
import Base from 'layouts/base.astro' import BaseLayout from 'layouts/base.astro'
import AlbumBox from 'components/AlbumBox.astro' import AlbumBox from 'components/AlbumBox.astro'
import DefaultSEO from 'components/DefaultSEO.astro'
const titles = [m.holy12_0(), m.holy12_1(), m.holy12_2(), m.holy12_3(), m.holy12_4(), m.holy12_5()] const titles = [m.holy12_0(), m.holy12_1(), m.holy12_2(), m.holy12_3(), m.holy12_4(), m.holy12_5()]
const title = getRandom(titles) const title = getRandom(titles)
const albums: { id: number; title: string }[] = await Promise.all(Array.from({ length: 12 }, getRandomAlbum)) const albums: { id: number; title: string }[] = await Promise.all(Array.from({ length: 12 }, getRandomAlbum))
--- ---
<Base> <DefaultSEO />
<BaseLayout>
<div class='flex flex-col px-28'> <div class='flex flex-col px-28'>
<h1 class='w-full uppercase font-medium tracking-wide text-4xl drop-shadow-2xl mt-5 mb-2 text-center'>{title}</h1> <h1 class='w-full uppercase font-medium tracking-wide text-4xl drop-shadow-2xl mt-5 mb-2 text-center'>{title}</h1>
<div class='grid grid-cols-2 md:grid-cols-4 gap-x-1.5 mb-4'> <div class='grid grid-cols-2 md:grid-cols-4 gap-x-1.5 mb-4'>
@ -23,4 +25,4 @@ const albums: { id: number; title: string }[] = await Promise.all(Array.from({ l
} }
</div> </div>
</div> </div>
</Base> </BaseLayout>

View file

@ -6,6 +6,7 @@ import { AlbumStatus } from '@prisma/client'
import Sidebar from 'components/Sidebar.astro' import Sidebar from 'components/Sidebar.astro'
import BaseLayout from 'layouts/base.astro' import BaseLayout from 'layouts/base.astro'
import AlbumBox from 'components/AlbumBox.astro' import AlbumBox from 'components/AlbumBox.astro'
import DefaultSEO from 'components/DefaultSEO.astro'
const recentAlbums = await prismaClient.albums.findMany({ const recentAlbums = await prismaClient.albums.findMany({
where: { status: AlbumStatus.SHOW }, where: { status: AlbumStatus.SHOW },
@ -21,6 +22,7 @@ const lastAlbums = await prismaClient.albums.findMany({
}) })
--- ---
<DefaultSEO />
<BaseLayout> <BaseLayout>
<div class='flex flex-col md:flex-row flex-1 max-w-[2000px]'> <div class='flex flex-col md:flex-row flex-1 max-w-[2000px]'>
<div class='flex-1 px-2'> <div class='flex-1 px-2'>
@ -50,7 +52,9 @@ const lastAlbums = await prismaClient.albums.findMany({
} }
</div> </div>
<div class='flex gap-x-2 py-4'> <div class='flex gap-x-2 py-4'>
<a href="/last-added" class='bg-dark uppercase rounded-md text-2xl p-1.5 font-semibold w-full'>{m.moreLastAdded}</a> <a href='/last-added' class='bg-dark uppercase rounded-md text-2xl p-1.5 font-semibold w-full'
>{m.moreLastAdded}</a
>
</div> </div>
</div> </div>
<Sidebar /> <Sidebar />

View file

@ -5,8 +5,10 @@ import { Image } from 'astro:assets'
import BaseLayout from '../layouts/base.astro' import BaseLayout from '../layouts/base.astro'
import GuraGif from '../img/assets/doggo-thumbs-up.gif' import GuraGif from '../img/assets/doggo-thumbs-up.gif'
import DefaultSEO from 'components/DefaultSEO.astro'
--- ---
<DefaultSEO />
<BaseLayout> <BaseLayout>
<div class='flex flex-1 flex-col bg-soc-green-dark py-6'> <div class='flex flex-1 flex-col bg-soc-green-dark py-6'>
<div class='text-md` text-center'> <div class='text-md` text-center'>

14
src/pages/requests.astro Normal file
View file

@ -0,0 +1,14 @@
---
import BaseLayout from 'layouts/base.astro'
import DefaultSEO from 'components/DefaultSEO.astro'
import RequestsTable from 'components/requests/RequestsTable'
import prismaClient from 'utils/prisma-client'
const initialRequests = await prismaClient.requests.findMany({ orderBy: { createdAt: 'desc' } })
---
<DefaultSEO />
<BaseLayout>
<RequestsTable client:only='react' initial={initialRequests} />
</BaseLayout>

View file

@ -3,6 +3,7 @@ import * as m from 'paraglide/messages'
import AlbumSearch from 'components/search/AlbumSearch.astro' import AlbumSearch from 'components/search/AlbumSearch.astro'
import BaseLayout from 'layouts/base.astro' import BaseLayout from 'layouts/base.astro'
import DefaultSEO from 'components/DefaultSEO.astro'
const query = (Astro.url.searchParams.get('q') ?? '').trim() const query = (Astro.url.searchParams.get('q') ?? '').trim()
const page = Astro.url.searchParams.get('page') || '1' const page = Astro.url.searchParams.get('page') || '1'
@ -10,6 +11,7 @@ const page = Astro.url.searchParams.get('page') || '1'
if (!query || query.length < 1) return Astro.redirect(404) if (!query || query.length < 1) return Astro.redirect(404)
--- ---
<DefaultSEO />
<BaseLayout> <BaseLayout>
<div class='w-full bg-dark'> <div class='w-full bg-dark'>
<div class='flex max-w-[1200px] mx-auto justify-center px-8 py-3 flex-col'> <div class='flex max-w-[1200px] mx-auto justify-center px-8 py-3 flex-col'>

12
src/schemas/requests.ts Normal file
View file

@ -0,0 +1,12 @@
import { RequestState } from '@prisma/client'
import { object, string, number, optional, enums } from 'superstruct'
export const EditRequest = object({
id: number(),
title: optional(string()),
link: optional(string()),
state: enums(Object.values(RequestState)),
reason: optional(string()),
comments: optional(string()),
message: optional(string())
})

View file

@ -6,6 +6,7 @@
"jsx": "react-jsx", "jsx": "react-jsx",
"jsxImportSource": "react", "jsxImportSource": "react",
"baseUrl": "src", "baseUrl": "src",
"strictNullChecks": true "strictNullChecks": true,
"allowJs": true
} }
} }

17670
yarn.lock

File diff suppressed because it is too large Load diff