Compare commits
41 Commits
containeri
...
main
Author | SHA1 | Date |
---|---|---|
|
e66ed6975d | |
|
872c1fd53a | |
|
c8c29fab75 | |
|
4459b9d644 | |
|
ae4e1685c5 | |
|
c0f85e89fd | |
|
f939c3896a | |
|
39ba6901d1 | |
|
e2b21601ea | |
|
59a8f8bc41 | |
|
01b98a0a08 | |
|
3c3564bb29 | |
|
cda903175c | |
|
e1044b58b7 | |
|
84caa342f0 | |
|
f9eb6254e3 | |
|
a5c40a7982 | |
|
da361e0f55 | |
|
65c23d4872 | |
|
e32df99446 | |
|
885f5cd56c | |
|
392110f55c | |
|
0230118f4f | |
|
4716264483 | |
|
5fb913b6a3 | |
|
19109b2b35 | |
|
447504667f | |
|
9179aab7d4 | |
|
3b63bec700 | |
|
a5c6f9d3cb | |
|
740a57b30e | |
|
b16d293e27 | |
|
01e7e20686 | |
|
1143cb5f22 | |
|
b9e94257b3 | |
|
8836b71111 | |
|
8995047aa5 | |
|
0bade3c1c9 | |
|
689364f3b6 | |
|
6ab29df9b6 | |
|
009433f483 |
|
@ -37,3 +37,12 @@ next-env.d.ts
|
|||
|
||||
#secret
|
||||
.env
|
||||
|
||||
#build
|
||||
/pack
|
||||
subman.tar.gz
|
||||
node_modules/
|
||||
/test-results/
|
||||
/playwright-report/
|
||||
/blob-report/
|
||||
/playwright/.cache/
|
||||
|
|
|
@ -7,14 +7,36 @@ agent any
|
|||
stages{
|
||||
stage('build'){
|
||||
steps{
|
||||
sh 'echo "JWT_SECRET=${JWT_SECRET}" | cat >> .env'
|
||||
sh 'echo "DATABASE_URL=${DATABASE_URL}" | cat >> .env'
|
||||
sh 'npm install'
|
||||
sh 'npm run build'
|
||||
}
|
||||
}
|
||||
stage('test'){
|
||||
steps{
|
||||
sh 'npx playwright install'
|
||||
sh 'npx playwright test'
|
||||
sh 'rm -r pack'
|
||||
}
|
||||
}
|
||||
stage('deploy'){
|
||||
steps{
|
||||
sshPublisher(publishers: [sshPublisherDesc(configName: 'Demos', transfers: [sshTransfer(cleanRemote: false, excludes: '', execCommand: '', execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory: 'ssh-uploads/subman', remoteDirectorySDF: false, removePrefix: '', sourceFiles: '**')], usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: false)])
|
||||
sshPublisher(publishers: [sshPublisherDesc(configName: 'Demos', transfers: [sshTransfer(cleanRemote: false, excludes: '', execCommand: 'ssh-uploads/subman/upgrade.sh', execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory: 'ssh-uploads/subman/', remoteDirectorySDF: false, removePrefix: '', sourceFiles: 'subman.tar.gz')], usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: false)])
|
||||
}
|
||||
}
|
||||
}
|
||||
post {
|
||||
// Clean after build
|
||||
always {
|
||||
cleanWs(cleanWhenNotBuilt: true,
|
||||
cleanWhenFailure: false,
|
||||
deleteDirs: true,
|
||||
disableDeferredWipeout: true,
|
||||
// notFailBuild: true,
|
||||
cleanWhenSuccess:false,
|
||||
patterns: [[pattern: '**/*', type: 'INCLUDE'],
|
||||
[pattern: '.propsfile', type: 'EXCLUDE']])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
output: "standalone",
|
||||
// basePath: "/subman",
|
||||
webpack: (config) => {
|
||||
config.externals = [...config.externals, "bcrypt"];
|
||||
return config;
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
"dependencies": {
|
||||
"@hookform/resolvers": "^3.6.0",
|
||||
"@mapbox/node-pre-gyp": "^1.0.11",
|
||||
"@next/env": "^14.2.14",
|
||||
"@prisma/client": "^5.15.0",
|
||||
"@radix-ui/react-checkbox": "^1.0.4",
|
||||
"@radix-ui/react-context-menu": "^2.2.1",
|
||||
|
@ -32,6 +33,7 @@
|
|||
"lucide-react": "^0.394.0",
|
||||
"next": "^14.2.13",
|
||||
"next-themes": "^0.3.0",
|
||||
"playwright": "^1.47.2",
|
||||
"react": "^18",
|
||||
"react-day-picker": "^8.10.1",
|
||||
"react-dom": "^18",
|
||||
|
@ -43,6 +45,7 @@
|
|||
"zod": "^3.23.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.47.2",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^18",
|
||||
"@types/react-dom": "^18",
|
||||
|
@ -227,10 +230,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@next/env": {
|
||||
"version": "14.2.13",
|
||||
"resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.13.tgz",
|
||||
"integrity": "sha512-s3lh6K8cbW1h5Nga7NNeXrbe0+2jIIYK9YaA9T7IufDWnZpozdFUp6Hf0d5rNWUKu4fEuSX2rCKlGjCrtylfDw==",
|
||||
"license": "MIT"
|
||||
"version": "14.2.14",
|
||||
"resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.14.tgz",
|
||||
"integrity": "sha512-/0hWQfiaD5//LvGNgc8PjvyqV50vGK0cADYzaoOOGN8fxzBn3iAiaq3S0tCRnFBldq0LVveLcxCTi41ZoYgAgg=="
|
||||
},
|
||||
"node_modules/@next/swc-darwin-arm64": {
|
||||
"version": "14.2.13",
|
||||
|
@ -408,6 +410,21 @@
|
|||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/@playwright/test": {
|
||||
"version": "1.47.2",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.47.2.tgz",
|
||||
"integrity": "sha512-jTXRsoSPONAs8Za9QEQdyjFn+0ZQFjCiIztAIF6bi1HqhBzG9Ma7g1WotyiGqFSBRZjIEqMdT8RUlbk1QVhzCQ==",
|
||||
"devOptional": true,
|
||||
"dependencies": {
|
||||
"playwright": "1.47.2"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@prisma/client": {
|
||||
"version": "5.16.0",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.16.0.tgz",
|
||||
|
@ -6630,6 +6647,11 @@
|
|||
"react-dom": "^16.8 || ^17 || ^18"
|
||||
}
|
||||
},
|
||||
"node_modules/next/node_modules/@next/env": {
|
||||
"version": "14.2.13",
|
||||
"resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.13.tgz",
|
||||
"integrity": "sha512-s3lh6K8cbW1h5Nga7NNeXrbe0+2jIIYK9YaA9T7IufDWnZpozdFUp6Hf0d5rNWUKu4fEuSX2rCKlGjCrtylfDw=="
|
||||
},
|
||||
"node_modules/next/node_modules/postcss": {
|
||||
"version": "8.4.31",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
|
||||
|
@ -7485,6 +7507,47 @@
|
|||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/playwright": {
|
||||
"version": "1.47.2",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.47.2.tgz",
|
||||
"integrity": "sha512-nx1cLMmQWqmA3UsnjaaokyoUpdVaaDhJhMoxX2qj3McpjnsqFHs516QAKYhqHAgOP+oCFTEOCOAaD1RgD/RQfA==",
|
||||
"dependencies": {
|
||||
"playwright-core": "1.47.2"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/playwright-core": {
|
||||
"version": "1.47.2",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.47.2.tgz",
|
||||
"integrity": "sha512-3JvMfF+9LJfe16l7AbSmU555PaTl2tPyQsVInqm3id16pdDfvZ8TTZ/pyzmkbDrZTQefyzU7AIHlZqQnxpqHVQ==",
|
||||
"bin": {
|
||||
"playwright-core": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/playwright/node_modules/fsevents": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
||||
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
||||
"hasInstallScript": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/possible-typed-array-names": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz",
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"build": "next build && ./package.sh",
|
||||
"start": "next start",
|
||||
"lint": "next lint",
|
||||
"tailwind": "npx tailwindcss -i ./src/app/globals.css -o ./src/app/tailwind.css --watch"
|
||||
|
@ -13,6 +13,7 @@
|
|||
"dependencies": {
|
||||
"@hookform/resolvers": "^3.6.0",
|
||||
"@mapbox/node-pre-gyp": "^1.0.11",
|
||||
"@next/env": "^14.2.14",
|
||||
"@prisma/client": "^5.15.0",
|
||||
"@radix-ui/react-checkbox": "^1.0.4",
|
||||
"@radix-ui/react-context-menu": "^2.2.1",
|
||||
|
@ -35,6 +36,7 @@
|
|||
"lucide-react": "^0.394.0",
|
||||
"next": "^14.2.13",
|
||||
"next-themes": "^0.3.0",
|
||||
"playwright": "^1.47.2",
|
||||
"react": "^18",
|
||||
"react-day-picker": "^8.10.1",
|
||||
"react-dom": "^18",
|
||||
|
@ -46,6 +48,7 @@
|
|||
"zod": "^3.23.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.47.2",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^18",
|
||||
"@types/react-dom": "^18",
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
mkdir pack
|
||||
cp -r public pack/
|
||||
cp -r prisma pack/
|
||||
cp -rT .next/standalone pack/
|
||||
cp -r .next/static pack/.next/
|
||||
|
||||
cd pack
|
||||
npx prisma db push
|
||||
cd ..
|
||||
|
||||
tar -cf subman.tar.gz pack
|
|
@ -0,0 +1,79 @@
|
|||
import { defineConfig, devices } from '@playwright/test';
|
||||
|
||||
/**
|
||||
* Read environment variables from file.
|
||||
* https://github.com/motdotla/dotenv
|
||||
*/
|
||||
// import dotenv from 'dotenv';
|
||||
// import path from 'path';
|
||||
// dotenv.config({ path: path.resolve(__dirname, '.env') });
|
||||
|
||||
/**
|
||||
* See https://playwright.dev/docs/test-configuration.
|
||||
*/
|
||||
export default defineConfig({
|
||||
testDir: './tests',
|
||||
/* Run tests in files in parallel */
|
||||
fullyParallel: true,
|
||||
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
||||
forbidOnly: !!process.env.CI,
|
||||
/* Retry on CI only */
|
||||
retries: process.env.CI ? 2 : 0,
|
||||
/* Opt out of parallel tests on CI. */
|
||||
workers: process.env.CI ? 1 : undefined,
|
||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||
reporter: 'html',
|
||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||
use: {
|
||||
/* Base URL to use in actions like `await page.goto('/')`. */
|
||||
baseURL: 'http://127.0.0.1:3000',
|
||||
|
||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||
trace: 'on-first-retry',
|
||||
},
|
||||
|
||||
/* Configure projects for major browsers */
|
||||
projects: [
|
||||
{
|
||||
name: 'chromium',
|
||||
use: { ...devices['Desktop Chrome'] },
|
||||
},
|
||||
|
||||
{
|
||||
name: 'firefox',
|
||||
use: { ...devices['Desktop Firefox'] },
|
||||
},
|
||||
|
||||
// {
|
||||
// name: 'webkit',
|
||||
// use: { ...devices['Desktop Safari'] },
|
||||
// },
|
||||
|
||||
/* Test against mobile viewports. */
|
||||
// {
|
||||
// name: 'Mobile Chrome',
|
||||
// use: { ...devices['Pixel 5'] },
|
||||
// },
|
||||
// {
|
||||
// name: 'Mobile Safari',
|
||||
// use: { ...devices['iPhone 12'] },
|
||||
// },
|
||||
|
||||
/* Test against branded browsers. */
|
||||
// {
|
||||
// name: 'Microsoft Edge',
|
||||
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
|
||||
// },
|
||||
// {
|
||||
// name: 'Google Chrome',
|
||||
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
|
||||
// },
|
||||
],
|
||||
|
||||
/* Run your local dev server before starting the tests */
|
||||
webServer: {
|
||||
command: 'node pack/server.js',
|
||||
url: 'http://127.0.0.1:3000',
|
||||
reuseExistingServer: !process.env.CI,
|
||||
},
|
||||
});
|
BIN
prisma/dev.db
BIN
prisma/dev.db
Binary file not shown.
|
@ -3,7 +3,11 @@ import prisma from 'app/lib/db';
|
|||
import { jwtVerify, JWTPayload, decodeJwt, SignJWT } from 'jose';
|
||||
import { cookies } from 'next/headers';
|
||||
import { loginSchema, LoginSchema } from 'app/login/schema';
|
||||
import { NextResponse } from 'next/server';
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
export async function getJWTSecretKey<Uint8Array>() {
|
||||
const secret = process.env.JWT_SECRET
|
||||
|
|
|
@ -42,12 +42,12 @@ export default function RootLayout({
|
|||
<footer className="my-auto md:mt-auto flex justify-center"><ModeToggle /><LogoutButton />
|
||||
</footer>
|
||||
</div>
|
||||
<div className="flex justify-center w-full">
|
||||
<div className="flex justify-center w-screen">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Toaster />
|
||||
<Toaster test-id="toast" />
|
||||
</ThemeProvider>
|
||||
</body>
|
||||
</html >
|
||||
|
|
|
@ -36,8 +36,7 @@ export default function LoginForm() {
|
|||
toast({ title: "login successful!" })
|
||||
setSubmitted(true)
|
||||
await revalidate(redirect)
|
||||
//BUG:the first time user logs in, page refreshes instead of redirecting
|
||||
router.push(redirect)
|
||||
window.location.href = redirect
|
||||
} else {
|
||||
toast({ title: "login failed!" })
|
||||
}
|
||||
|
@ -45,10 +44,15 @@ export default function LoginForm() {
|
|||
|
||||
|
||||
return (
|
||||
<>
|
||||
{submitted ? <p>Logging in...</p> :
|
||||
<main className="flex flex-col items-center justify-around h-60 w-26">
|
||||
{submitted ? <div className="flex flex-col items-center justify-around h-30 w-26">
|
||||
<h1>Logging in...</h1>
|
||||
<Button asChild>
|
||||
<Link href={redirect}>Continue</Link>
|
||||
</Button>
|
||||
</div> :
|
||||
<Form {...form}>
|
||||
<form onSubmit={onSubmit} className="mt-20 flex flex-col items-center space-y-6">
|
||||
<form onSubmit={onSubmit} className="flex flex-col items-center space-y-6">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="email"
|
||||
|
@ -79,7 +83,7 @@ export default function LoginForm() {
|
|||
</form>
|
||||
</Form>
|
||||
}
|
||||
</>
|
||||
</main>
|
||||
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,13 +1,28 @@
|
|||
import Image from "next/image";
|
||||
import styles from "./page.module.css";
|
||||
import Link from "next/link";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<main className={styles.main}>
|
||||
Hello
|
||||
<h1 className="text-3xl font-black underline">
|
||||
Hello world!
|
||||
</h1>
|
||||
</main>
|
||||
<main className="flex flex-col gap-4 items-center justify-around h-60 w-26 m-6">
|
||||
< div >
|
||||
<h1 className="text-3xl font-black">
|
||||
Welcome to Subman!
|
||||
</h1>
|
||||
</div >
|
||||
<div className="flex flex-col gap-3">
|
||||
<p>
|
||||
This app is for demonstration purposes only. Data is reset periodically.
|
||||
</p>
|
||||
<p>
|
||||
<b>USERNAME:</b> demo@demo.demo
|
||||
</p>
|
||||
<p>
|
||||
<b>PASSWORD:</b> password
|
||||
</p>
|
||||
</div>
|
||||
<Button className="mt-6">
|
||||
<Link href="/login">Log in</Link>
|
||||
</Button>
|
||||
</main >
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
"use client"
|
||||
import { Dialog, DialogHeader, DialogTrigger, DialogContent, DialogClose, DialogTitle, DialogFooter, DialogDescription } from "@/components/ui/dialog";
|
||||
import { DialogHeader, DialogTitle, DialogFooter, DialogDescription } from "@/components/ui/dialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { ComponentProps } from "react";
|
||||
import { Genre, Pub } from "@prisma/client";
|
||||
import { createPub } from "app/lib/create";
|
||||
import PubForm from "app/ui/forms/pub";
|
||||
import { Plus } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { PubWithGenres } from "./page";
|
||||
|
||||
export default function EditPubDialog({ genres, closeDialog, defaults, dbAction }: ComponentProps<"div"> & { genres: Genre[], closeDialog: () => void, defaults: PubWithGenres, dbAction: (data: Pub & { genres: number[] }) => Promise<{ success: string }> }) {
|
||||
|
@ -17,7 +14,7 @@ export default function EditPubDialog({ genres, closeDialog, defaults, dbAction
|
|||
<>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Edit publication</DialogTitle>
|
||||
<DialogDescription>Modify an entry for an existing publication.</DialogDescription>
|
||||
<DialogDescription>Modify an entry for an existing publication. Remember - you can edit fields inline by double clicking on them!</DialogDescription>
|
||||
</DialogHeader>
|
||||
<PubForm dbAction={dbAction} genres={genres} closeDialog={closeDialog} defaults={defaults} />
|
||||
<DialogFooter>
|
||||
|
|
|
@ -14,7 +14,7 @@ export default function EditStoryDialog({ genres, closeDialog, defaults, dbActio
|
|||
<>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Edit story</DialogTitle>
|
||||
<DialogDescription>Create an entry for a new story i.e. a thing you intend to submit for publication.</DialogDescription>
|
||||
<DialogDescription>Modify an entry for an existing story. Remember - you can edit fields inline by double-clicking on them!</DialogDescription>
|
||||
</DialogHeader>
|
||||
<StoryForm dbAction={dbAction} genres={genres} className="" closeDialog={closeDialog} defaults={defaults} />
|
||||
<DialogFooter>
|
||||
|
|
|
@ -5,7 +5,6 @@ import { Button } from "@/components/ui/button"
|
|||
import { SubComplete } from "./page"
|
||||
import { selectCol } from "app/ui/tables/selectColumn"
|
||||
import TitleContainer from "app/ui/titleContainer"
|
||||
import { CalendarArrowUp } from "lucide"
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -8,10 +8,12 @@ import { Pub, Response, Story } from "@prisma/client";
|
|||
import SubmissionForm from "app/ui/forms/sub";
|
||||
import { Plus } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { StoryWithGenres } from "app/story/page";
|
||||
import { PubWithGenres } from "app/publication/page";
|
||||
|
||||
|
||||
|
||||
export default function CreateSubmissionDialog({ stories, pubs, responses }: ComponentProps<"div"> & { stories: Story[], pubs: Pub[], responses: Response[] }) {
|
||||
export default function CreateSubmissionDialog({ stories, pubs, responses }: ComponentProps<"div"> & { stories: StoryWithGenres[], pubs: PubWithGenres[], responses: Response[] }) {
|
||||
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
function closeDialog() {
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
"use server"
|
||||
import { getPubs, getResponses, getStories } from "app/lib/get";
|
||||
import SubmissionForm from "app/ui/forms/sub";
|
||||
import prisma from "app/lib/db";
|
||||
import { CreateContainer, CreateContainerContent, CreateContainerHeader } from "app/ui/createContainer";
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
export default async function Page() {
|
||||
const stories = await getStories()
|
||||
const pubs = await getPubs()
|
||||
const responses = await getResponses()
|
||||
|
||||
return (
|
||||
<CreateContainer>
|
||||
<CreateContainerHeader>New submission</CreateContainerHeader>
|
||||
<CreateContainerContent>
|
||||
<SubmissionForm stories={stories} pubs={pubs} responses={responses} />
|
||||
</CreateContainerContent>
|
||||
</CreateContainer>
|
||||
)
|
||||
}
|
|
@ -1,8 +1,10 @@
|
|||
import { getGenres, getPubs, getResponses, getStories, getSubsComplete } from "app/lib/get"
|
||||
import { getGenres, getPubs, getPubsWithGenres, getResponses, getStories, getStoriesWithGenres, getSubsComplete } from "app/lib/get"
|
||||
import { DataTable } from "app/ui/tables/data-table"
|
||||
import { columns } from "./columns"
|
||||
import { Pub, Response, Story, Sub } from "@prisma/client"
|
||||
import { Genre, Pub, Response, Story, Sub } from "@prisma/client"
|
||||
import CreateSubmissionDialog from "./create"
|
||||
import { PubWithGenres } from "app/publication/page"
|
||||
import { StoryWithGenres } from "app/story/page"
|
||||
|
||||
export type SubComplete = Sub & {
|
||||
pub: Pub,
|
||||
|
@ -12,10 +14,10 @@ export type SubComplete = Sub & {
|
|||
export default async function Page() {
|
||||
|
||||
const subs: Array<SubComplete> = await getSubsComplete()
|
||||
const stories = await getStories()
|
||||
const pubs = await getPubs()
|
||||
const responses = await getResponses()
|
||||
const genres = await getGenres()
|
||||
const stories: StoryWithGenres[] = await getStoriesWithGenres()
|
||||
const pubs: PubWithGenres[] = await getPubsWithGenres()
|
||||
const responses: Response[] = await getResponses()
|
||||
const genres: Genre[] = await getGenres()
|
||||
|
||||
return (
|
||||
<div className="container px-1 md:px-4 mx-auto">
|
||||
|
|
|
@ -706,6 +706,10 @@ body {
|
|||
z-index: 100;
|
||||
}
|
||||
|
||||
.m-6 {
|
||||
margin: 1.5rem;
|
||||
}
|
||||
|
||||
.m-auto {
|
||||
margin: auto;
|
||||
}
|
||||
|
@ -715,6 +719,11 @@ body {
|
|||
margin-right: -0.25rem;
|
||||
}
|
||||
|
||||
.mx-0 {
|
||||
margin-left: 0px;
|
||||
margin-right: 0px;
|
||||
}
|
||||
|
||||
.mx-1 {
|
||||
margin-left: 0.25rem;
|
||||
margin-right: 0.25rem;
|
||||
|
@ -765,10 +774,6 @@ body {
|
|||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.mt-20 {
|
||||
margin-top: 5rem;
|
||||
}
|
||||
|
||||
.mt-3 {
|
||||
margin-top: 0.75rem;
|
||||
}
|
||||
|
@ -850,6 +855,10 @@ body {
|
|||
height: 1.25rem;
|
||||
}
|
||||
|
||||
.h-60 {
|
||||
height: 15rem;
|
||||
}
|
||||
|
||||
.h-7 {
|
||||
height: 1.75rem;
|
||||
}
|
||||
|
@ -1133,6 +1142,10 @@ body {
|
|||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.gap-3 {
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.gap-4 {
|
||||
gap: 1rem;
|
||||
}
|
||||
|
@ -1612,10 +1625,6 @@ body {
|
|||
color: rgb(255 255 255 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.underline {
|
||||
text-decoration-line: underline;
|
||||
}
|
||||
|
||||
.underline-offset-4 {
|
||||
text-underline-offset: 4px;
|
||||
}
|
||||
|
|
|
@ -36,12 +36,14 @@ import { createSub } from "app/lib/create"
|
|||
import { subSchema } from "./schemas"
|
||||
import { useRouter } from "next/navigation"
|
||||
import { Ban } from "lucide-react"
|
||||
import { Story } from "@prisma/client"
|
||||
import { Pub, Response, Story, Sub } from "@prisma/client"
|
||||
import { StoryWithGenres } from "app/story/page"
|
||||
import { PubWithGenres } from "app/publication/page"
|
||||
|
||||
export type SubForm = z.infer<typeof subSchema>
|
||||
|
||||
|
||||
export default function SubmissionForm({ stories, pubs, responses, defaults, closeDialog }: { stories: any, pubs: any, responses: any, defaults?: any, closeDialog?: () => void }) {
|
||||
export default function SubmissionForm({ stories, pubs, responses, defaults, closeDialog }: { stories: StoryWithGenres[], pubs: PubWithGenres[], responses: Response[], defaults?: Sub, closeDialog?: () => void }) {
|
||||
const form = useForm<z.infer<typeof subSchema>>({
|
||||
resolver: zodResolver(subSchema),
|
||||
defaultValues: {
|
||||
|
@ -51,19 +53,41 @@ export default function SubmissionForm({ stories, pubs, responses, defaults, clo
|
|||
})
|
||||
const [isSubCalendarOpen, setIsSubCalendarOpen] = useState(false);
|
||||
const [isRespCalendarOpen, setIsRespCalendarOpen] = useState(false);
|
||||
const [relevantPubIds, setRelevantPubIds] = useState(pubs.map(e => e.id));
|
||||
|
||||
function updateRelevantPubs(storyId: number) {
|
||||
console.log("storyId: " + storyId)
|
||||
console.log("stories: ", stories)
|
||||
const story = stories.find(e => e.id == storyId)
|
||||
console.log("story: ", story)
|
||||
const storyGenreIds = story?.genres.map(e => e.id) ?? []
|
||||
const relevantPubIds = pubs.filter(e => {
|
||||
const pubGenreIds = e.genres.map(e => e.id)
|
||||
for (let i = 0; i < storyGenreIds.length; i++) {
|
||||
const storyGenreId = storyGenreIds[i];
|
||||
if (pubGenreIds.includes(storyGenreId)) return true
|
||||
}
|
||||
}).map(e => e.id)
|
||||
console.log("relevant pubs: ", relevantPubIds)
|
||||
setRelevantPubIds(relevantPubIds)
|
||||
}
|
||||
|
||||
const storiesSelectItems = stories.map((e: Story) => (
|
||||
<SelectItem value={e.id?.toString()} key={e.title}>
|
||||
{e.title}
|
||||
</SelectItem>
|
||||
))
|
||||
const pubsSelectItems = pubs.map(e => (
|
||||
<SelectItem value={e.id} key={e.title}>
|
||||
{e.title}
|
||||
</SelectItem>
|
||||
))
|
||||
const pubsSelectItems = pubs.map(e => {
|
||||
const isDisabled = !relevantPubIds.includes(e.id)
|
||||
return (
|
||||
<SelectItem disabled={isDisabled} value={e.id.toString()} key={e.title}>
|
||||
{e.title}
|
||||
</SelectItem>
|
||||
)
|
||||
})
|
||||
|
||||
const reponsesSelectItems = responses.map(e => (
|
||||
<SelectItem value={e.id} key={e.title}>
|
||||
<SelectItem value={e.id.toString()} key={e.response}>
|
||||
{e.response}
|
||||
</SelectItem>
|
||||
))
|
||||
|
@ -127,7 +151,8 @@ export default function SubmissionForm({ stories, pubs, responses, defaults, clo
|
|||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel className="text-sm md:text-base">Publication</FormLabel>
|
||||
<Select onValueChange={field.onChange} defaultValue={field.value?.toString()}>
|
||||
<Select onOpenChange={() => updateRelevantPubs(form.getValues().storyId
|
||||
)} onValueChange={field.onChange} defaultValue={field.value?.toString()}>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select something">
|
||||
|
@ -139,7 +164,7 @@ export default function SubmissionForm({ stories, pubs, responses, defaults, clo
|
|||
{pubsSelectItems}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormDescription className="text-xs md:text-base">The market you sent it to</FormDescription>
|
||||
<FormDescription className="text-xs md:text-base">The market you sent it to. Bad genre fits are greyed out.</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
|
|
|
@ -2,8 +2,6 @@
|
|||
import { Button } from "@/components/ui/button"
|
||||
import {
|
||||
ContextMenu,
|
||||
ContextMenuContent,
|
||||
ContextMenuItem,
|
||||
ContextMenuTrigger,
|
||||
} from "@/components/ui/context-menu"
|
||||
import {
|
||||
|
@ -15,7 +13,7 @@ import {
|
|||
DropdownMenuRadioGroup
|
||||
} from "@/components/ui/dropdown-menu"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Component, ComponentProps, use, useState } from "react"
|
||||
import { ComponentProps, useState } from "react"
|
||||
import {
|
||||
ColumnDef,
|
||||
flexRender,
|
||||
|
@ -38,7 +36,7 @@ import {
|
|||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/components/ui/table"
|
||||
import { EyeIcon, Trash2 } from "lucide-react"
|
||||
import { CircleHelp, EyeIcon, Filter, Trash2 } from "lucide-react"
|
||||
import { usePathname, useSearchParams } from "next/navigation"
|
||||
import FormContextMenu from "./contextMenu"
|
||||
import { deleteRecord, deleteRecords } from "app/lib/del"
|
||||
|
@ -129,16 +127,14 @@ export function DataTable<TData, TValue>({
|
|||
|
||||
const router = useRouter()
|
||||
|
||||
const [filterBy, setFilterBy] = useState(table.getAllColumns()[0])
|
||||
const [filterBy, setFilterBy] = useState(table.getAllColumns().filter(e => e.getCanFilter())[0])
|
||||
const [isContextMenuOpen, setIsContextMenuOpen] = useState(false)
|
||||
return (<>
|
||||
<div className="flex justify-between items-center py-1 md:py-4">
|
||||
<div className="flex gap-2">
|
||||
<div className="flex gap-2 justify-between items-center py-1 md:py-4">
|
||||
<div className="flex gap-1">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="outline" className="hidden sm:block ml-auto">
|
||||
Filter by
|
||||
</Button>
|
||||
<Button variant="outline" className="mx-0"> <p className="hidden md:block">Filter by</p><Filter className="block md:hidden" /> </Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
{/*@ts-ignore*/}
|
||||
|
@ -308,36 +304,45 @@ export function DataTable<TData, TValue>({
|
|||
</TableHeader>
|
||||
<TableBody>
|
||||
{table.getRowModel().rows?.length ? (
|
||||
table.getRowModel().rows.map((row) => (
|
||||
<ContextMenu key={row.id + "contextMenu"}>
|
||||
<ContextMenuTrigger asChild>
|
||||
<TableRow
|
||||
key={row.id}
|
||||
data-state={row.getIsSelected() && "selected"}
|
||||
tabIndex={0}
|
||||
onDoubleClick={() => {
|
||||
if (tableName === "sub") {
|
||||
openEditDialog(row)
|
||||
table.getRowModel().rows.map((row) => {
|
||||
const classes = () => {
|
||||
const classes = []
|
||||
if (row.getValue('response') === "Pending") classes.push("bg-accent")
|
||||
if (row.getValue('response') === "Acceptance") classes.push("bg-primary")
|
||||
return classes.join(" ")
|
||||
}
|
||||
return (
|
||||
<ContextMenu key={row.id + "contextMenu"}>
|
||||
<ContextMenuTrigger asChild>
|
||||
<TableRow
|
||||
key={row.id}
|
||||
className={classes()}
|
||||
data-state={row.getIsSelected() && "selected"}
|
||||
tabIndex={0}
|
||||
onDoubleClick={() => {
|
||||
if (tableName === "sub") {
|
||||
openEditDialog(row)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
>
|
||||
{row.getVisibleCells().map((cell) => (
|
||||
<TableCell key={cell.id}>
|
||||
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||
</TableCell>
|
||||
))}
|
||||
<FormContextMenu
|
||||
key={"formContextMenu" + row.id}
|
||||
row={row}
|
||||
table={table}
|
||||
openEditDialog={openEditDialog}
|
||||
openDeleteDialog={openDeleteDialog}
|
||||
/>
|
||||
</TableRow>
|
||||
</ContextMenuTrigger>
|
||||
</ContextMenu>
|
||||
))
|
||||
}
|
||||
>
|
||||
{row.getVisibleCells().map((cell) => (
|
||||
<TableCell key={cell.id}>
|
||||
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||
</TableCell>
|
||||
))}
|
||||
<FormContextMenu
|
||||
key={"formContextMenu" + row.id}
|
||||
row={row}
|
||||
table={table}
|
||||
openEditDialog={openEditDialog}
|
||||
openDeleteDialog={openDeleteDialog}
|
||||
/>
|
||||
</TableRow>
|
||||
</ContextMenuTrigger>
|
||||
</ContextMenu>
|
||||
)
|
||||
})
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell colSpan={columns.length} className="h-24 text-center">
|
||||
|
|
|
@ -16,7 +16,7 @@ export const selectCol = {
|
|||
|
||||
)
|
||||
},
|
||||
|
||||
enableColumnFilter: false,
|
||||
cell: (props: CellContext<any, any>) => {
|
||||
return (
|
||||
<div className="flex items-start justify-left">
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
import { test, expect } from '@playwright/test'
|
||||
|
||||
test('should redirect to login page if not logged in', async ({ page }) => {
|
||||
await page.goto('/')
|
||||
await page.click('text=Stories')
|
||||
await expect(page).toHaveURL('/login?from=%2Fstory')
|
||||
await page.click('text=Publications')
|
||||
await expect(page).toHaveURL('/login?from=%2Fpublication')
|
||||
await page.click('text=Submissions')
|
||||
await expect(page).toHaveURL('/login?from=%2Fsubmission')
|
||||
})
|
||||
|
||||
test('positive login', async ({ page }) => {
|
||||
await page.goto('/login')
|
||||
await page.getByRole('textbox', { name: 'email' }).fill('demo@demo.demo')
|
||||
await page.getByRole('textbox', { name: 'password' }).fill('password')
|
||||
await page.getByRole('button', { name: 'submit' }).click()
|
||||
|
||||
await page.waitForURL('**/submission', { timeout: 5000 })
|
||||
await expect(page).toHaveURL('/submission');
|
||||
|
||||
})
|
||||
|
||||
test('negative login', async ({ page }) => {
|
||||
|
||||
await page.goto('/login')
|
||||
await page.getByRole('textbox', { name: 'email' }).fill('demo@demo.negative')
|
||||
await page.getByRole('textbox', { name: 'password' }).fill('negative')
|
||||
await page.getByRole('button', { name: 'submit' }).click()
|
||||
await expect(page.getByText("login failed!")).toBeTruthy()
|
||||
})
|
Loading…
Reference in New Issue