Create project schema and scaffold + some minor changes
This commit is contained in:
49
api/db/migrations/20240824001030_project/migration.sql
Normal file
49
api/db/migrations/20240824001030_project/migration.sql
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "Tag" (
|
||||||
|
"id" SERIAL NOT NULL,
|
||||||
|
"tag" TEXT NOT NULL,
|
||||||
|
"color" TEXT NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "Tag_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "ProjectImage" (
|
||||||
|
"id" SERIAL NOT NULL,
|
||||||
|
"fileId" TEXT NOT NULL,
|
||||||
|
"projectId" INTEGER,
|
||||||
|
|
||||||
|
CONSTRAINT "ProjectImage_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "Project" (
|
||||||
|
"id" SERIAL NOT NULL,
|
||||||
|
"title" TEXT NOT NULL,
|
||||||
|
"description" TEXT NOT NULL DEFAULT 'No description provided',
|
||||||
|
"date" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"links" TEXT[] DEFAULT ARRAY[]::TEXT[],
|
||||||
|
|
||||||
|
CONSTRAINT "Project_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "_ProjectToTag" (
|
||||||
|
"A" INTEGER NOT NULL,
|
||||||
|
"B" INTEGER NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "_ProjectToTag_AB_unique" ON "_ProjectToTag"("A", "B");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "_ProjectToTag_B_index" ON "_ProjectToTag"("B");
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "ProjectImage" ADD CONSTRAINT "ProjectImage_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "_ProjectToTag" ADD CONSTRAINT "_ProjectToTag_A_fkey" FOREIGN KEY ("A") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "_ProjectToTag" ADD CONSTRAINT "_ProjectToTag_B_fkey" FOREIGN KEY ("B") REFERENCES "Tag"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
@@ -48,3 +48,28 @@ model Portrait {
|
|||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
fileId String
|
fileId String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
model Tag {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
tag String
|
||||||
|
color String
|
||||||
|
projects Project[]
|
||||||
|
}
|
||||||
|
|
||||||
|
model ProjectImage {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
fileId String
|
||||||
|
|
||||||
|
Project Project? @relation(fields: [projectId], references: [id])
|
||||||
|
projectId Int?
|
||||||
|
}
|
||||||
|
|
||||||
|
model Project {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
title String
|
||||||
|
description String @default("No description provided")
|
||||||
|
images ProjectImage[]
|
||||||
|
date DateTime @default(now())
|
||||||
|
links String[] @default([])
|
||||||
|
tags Tag[]
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
"@redwoodjs/graphql-server": "7.7.4",
|
"@redwoodjs/graphql-server": "7.7.4",
|
||||||
"@tus/file-store": "^1.4.0",
|
"@tus/file-store": "^1.4.0",
|
||||||
"@tus/server": "^1.7.0",
|
"@tus/server": "^1.7.0",
|
||||||
|
"graphql-scalars": "^1.23.0",
|
||||||
"nodemailer": "^6.9.14"
|
"nodemailer": "^6.9.14"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -2,6 +2,9 @@
|
|||||||
"globals": {
|
"globals": {
|
||||||
"context": {
|
"context": {
|
||||||
"writable": false
|
"writable": false
|
||||||
|
},
|
||||||
|
"gql": {
|
||||||
|
"writable": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
import { mockRedwoodDirective, getDirectiveName } from '@redwoodjs/testing/api'
|
|
||||||
|
|
||||||
import requireAuth from './requireAuth'
|
|
||||||
|
|
||||||
describe('requireAuth directive', () => {
|
|
||||||
it('declares the directive sdl as schema, with the correct name', () => {
|
|
||||||
expect(requireAuth.schema).toBeTruthy()
|
|
||||||
expect(getDirectiveName(requireAuth.schema)).toBe('requireAuth')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('requireAuth has stub implementation. Should not throw when current user', () => {
|
|
||||||
// If you want to set values in context, pass it through e.g.
|
|
||||||
// mockRedwoodDirective(requireAuth, { context: { currentUser: { id: 1, name: 'Lebron McGretzky' } }})
|
|
||||||
const mockExecution = mockRedwoodDirective(requireAuth, { context: {} })
|
|
||||||
|
|
||||||
expect(mockExecution).not.toThrowError()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
import { getDirectiveName } from '@redwoodjs/testing/api'
|
|
||||||
|
|
||||||
import skipAuth from './skipAuth'
|
|
||||||
|
|
||||||
describe('skipAuth directive', () => {
|
|
||||||
it('declares the directive sdl as schema, with the correct name', () => {
|
|
||||||
expect(skipAuth.schema).toBeTruthy()
|
|
||||||
expect(getDirectiveName(skipAuth.schema)).toBe('skipAuth')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { URLTypeDefinition, URLResolver } from 'graphql-scalars'
|
||||||
|
|
||||||
import { createAuthDecoder } from '@redwoodjs/auth-dbauth-api'
|
import { createAuthDecoder } from '@redwoodjs/auth-dbauth-api'
|
||||||
import { createGraphQLHandler } from '@redwoodjs/graphql-server'
|
import { createGraphQLHandler } from '@redwoodjs/graphql-server'
|
||||||
|
|
||||||
@@ -18,6 +20,12 @@ export const handler = createGraphQLHandler({
|
|||||||
directives,
|
directives,
|
||||||
sdls,
|
sdls,
|
||||||
services,
|
services,
|
||||||
|
schemaOptions: {
|
||||||
|
typeDefs: [URLTypeDefinition],
|
||||||
|
resolvers: {
|
||||||
|
URL: URLResolver,
|
||||||
|
},
|
||||||
|
},
|
||||||
onException: () => {
|
onException: () => {
|
||||||
// Disconnect from your database with an unhandled exception.
|
// Disconnect from your database with an unhandled exception.
|
||||||
db.$disconnect()
|
db.$disconnect()
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
export const schema = gql`
|
export const schema = gql`
|
||||||
type Portrait {
|
type Portrait {
|
||||||
id: Int!
|
id: Int!
|
||||||
fileId: String!
|
fileId: URL!
|
||||||
}
|
}
|
||||||
|
|
||||||
type Query {
|
type Query {
|
||||||
@@ -9,7 +9,7 @@ export const schema = gql`
|
|||||||
}
|
}
|
||||||
|
|
||||||
input CreatePortraitInput {
|
input CreatePortraitInput {
|
||||||
fileId: String!
|
fileId: URL!
|
||||||
}
|
}
|
||||||
|
|
||||||
type Mutation {
|
type Mutation {
|
||||||
33
api/src/graphql/projectImages.sdl.ts
Normal file
33
api/src/graphql/projectImages.sdl.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
export const schema = gql`
|
||||||
|
type ProjectImage {
|
||||||
|
id: Int!
|
||||||
|
fileId: URL!
|
||||||
|
Project: Project
|
||||||
|
projectId: Int
|
||||||
|
}
|
||||||
|
|
||||||
|
type Query {
|
||||||
|
projectImages: [ProjectImage!]! @requireAuth
|
||||||
|
projectImage(id: Int!): ProjectImage @requireAuth
|
||||||
|
}
|
||||||
|
|
||||||
|
input CreateProjectImageInput {
|
||||||
|
fileId: URL!
|
||||||
|
projectId: Int
|
||||||
|
}
|
||||||
|
|
||||||
|
input UpdateProjectImageInput {
|
||||||
|
fileId: URL
|
||||||
|
projectId: Int
|
||||||
|
}
|
||||||
|
|
||||||
|
type Mutation {
|
||||||
|
createProjectImage(input: CreateProjectImageInput!): ProjectImage!
|
||||||
|
@requireAuth
|
||||||
|
updateProjectImage(
|
||||||
|
id: Int!
|
||||||
|
input: UpdateProjectImageInput!
|
||||||
|
): ProjectImage! @requireAuth
|
||||||
|
deleteProjectImage(id: Int!): ProjectImage! @requireAuth
|
||||||
|
}
|
||||||
|
`
|
||||||
36
api/src/graphql/projects.sdl.ts
Normal file
36
api/src/graphql/projects.sdl.ts
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
export const schema = gql`
|
||||||
|
type Project {
|
||||||
|
id: Int!
|
||||||
|
title: String!
|
||||||
|
description: String!
|
||||||
|
images: [ProjectImage]!
|
||||||
|
date: DateTime!
|
||||||
|
links: [String]!
|
||||||
|
tags: [Tag]!
|
||||||
|
}
|
||||||
|
|
||||||
|
type Query {
|
||||||
|
projects: [Project!]! @requireAuth
|
||||||
|
project(id: Int!): Project @requireAuth
|
||||||
|
}
|
||||||
|
|
||||||
|
input CreateProjectInput {
|
||||||
|
title: String!
|
||||||
|
description: String!
|
||||||
|
date: DateTime!
|
||||||
|
links: [String]!
|
||||||
|
}
|
||||||
|
|
||||||
|
input UpdateProjectInput {
|
||||||
|
title: String
|
||||||
|
description: String
|
||||||
|
date: DateTime
|
||||||
|
links: [String]!
|
||||||
|
}
|
||||||
|
|
||||||
|
type Mutation {
|
||||||
|
createProject(input: CreateProjectInput!): Project! @requireAuth
|
||||||
|
updateProject(id: Int!, input: UpdateProjectInput!): Project! @requireAuth
|
||||||
|
deleteProject(id: Int!): Project! @requireAuth
|
||||||
|
}
|
||||||
|
`
|
||||||
3
api/src/graphql/scalars.sdl.ts
Normal file
3
api/src/graphql/scalars.sdl.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export const schema = gql`
|
||||||
|
scalar URL
|
||||||
|
`
|
||||||
29
api/src/graphql/tags.sdl.ts
Normal file
29
api/src/graphql/tags.sdl.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
export const schema = gql`
|
||||||
|
type Tag {
|
||||||
|
id: Int!
|
||||||
|
tag: String!
|
||||||
|
color: String!
|
||||||
|
projects: [Project]!
|
||||||
|
}
|
||||||
|
|
||||||
|
type Query {
|
||||||
|
tags: [Tag!]! @requireAuth
|
||||||
|
tag(id: Int!): Tag @requireAuth
|
||||||
|
}
|
||||||
|
|
||||||
|
input CreateTagInput {
|
||||||
|
tag: String!
|
||||||
|
color: String!
|
||||||
|
}
|
||||||
|
|
||||||
|
input UpdateTagInput {
|
||||||
|
tag: String
|
||||||
|
color: String
|
||||||
|
}
|
||||||
|
|
||||||
|
type Mutation {
|
||||||
|
createTag(input: CreateTagInput!): Tag! @requireAuth
|
||||||
|
updateTag(id: Int!, input: UpdateTagInput!): Tag! @requireAuth
|
||||||
|
deleteTag(id: Int!): Tag! @requireAuth
|
||||||
|
}
|
||||||
|
`
|
||||||
@@ -6,7 +6,7 @@ import {
|
|||||||
ValidationError,
|
ValidationError,
|
||||||
} from '@redwoodjs/graphql-server'
|
} from '@redwoodjs/graphql-server'
|
||||||
|
|
||||||
import { db } from './db'
|
import { db } from 'src/lib/db'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The name of the cookie that dbAuth sets
|
* The name of the cookie that dbAuth sets
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { PrismaClient } from '@prisma/client'
|
|||||||
|
|
||||||
import { emitLogLevels, handlePrismaLogging } from '@redwoodjs/api/logger'
|
import { emitLogLevels, handlePrismaLogging } from '@redwoodjs/api/logger'
|
||||||
|
|
||||||
import { logger } from './logger'
|
import { logger } from 'src/lib/logger'
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Instance of the Prisma Client
|
* Instance of the Prisma Client
|
||||||
|
|||||||
@@ -1,9 +1,14 @@
|
|||||||
import type { QueryResolvers, MutationResolvers } from 'types/graphql'
|
import type { QueryResolvers, MutationResolvers } from 'types/graphql'
|
||||||
|
|
||||||
|
import { isProduction } from '@redwoodjs/api/dist/logger'
|
||||||
import { ValidationError } from '@redwoodjs/graphql-server'
|
import { ValidationError } from '@redwoodjs/graphql-server'
|
||||||
|
|
||||||
import { db } from 'src/lib/db'
|
import { db } from 'src/lib/db'
|
||||||
|
|
||||||
|
const address = isProduction
|
||||||
|
? process.env.ADDRESS_PROD
|
||||||
|
: process.env.ADDRESS_DEV
|
||||||
|
|
||||||
export const portrait: QueryResolvers['portrait'] = async () => {
|
export const portrait: QueryResolvers['portrait'] = async () => {
|
||||||
const portrait = await db.portrait.findFirst()
|
const portrait = await db.portrait.findFirst()
|
||||||
|
|
||||||
@@ -11,7 +16,7 @@ export const portrait: QueryResolvers['portrait'] = async () => {
|
|||||||
else
|
else
|
||||||
return {
|
return {
|
||||||
id: -1,
|
id: -1,
|
||||||
fileId: '/no_portrait.webp',
|
fileId: `${address}/no_portrait.webp`,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
49
api/src/services/projectImages/projectImages.ts
Normal file
49
api/src/services/projectImages/projectImages.ts
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import type {
|
||||||
|
QueryResolvers,
|
||||||
|
MutationResolvers,
|
||||||
|
ProjectImageRelationResolvers,
|
||||||
|
} from 'types/graphql'
|
||||||
|
|
||||||
|
import { db } from 'src/lib/db'
|
||||||
|
|
||||||
|
export const projectImages: QueryResolvers['projectImages'] = () => {
|
||||||
|
return db.projectImage.findMany()
|
||||||
|
}
|
||||||
|
|
||||||
|
export const projectImage: QueryResolvers['projectImage'] = ({ id }) => {
|
||||||
|
return db.projectImage.findUnique({
|
||||||
|
where: { id },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createProjectImage: MutationResolvers['createProjectImage'] = ({
|
||||||
|
input,
|
||||||
|
}) => {
|
||||||
|
return db.projectImage.create({
|
||||||
|
data: input,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const updateProjectImage: MutationResolvers['updateProjectImage'] = ({
|
||||||
|
id,
|
||||||
|
input,
|
||||||
|
}) => {
|
||||||
|
return db.projectImage.update({
|
||||||
|
data: input,
|
||||||
|
where: { id },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const deleteProjectImage: MutationResolvers['deleteProjectImage'] = ({
|
||||||
|
id,
|
||||||
|
}) => {
|
||||||
|
return db.projectImage.delete({
|
||||||
|
where: { id },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ProjectImage: ProjectImageRelationResolvers = {
|
||||||
|
Project: (_obj, { root }) => {
|
||||||
|
return db.projectImage.findUnique({ where: { id: root?.id } }).Project()
|
||||||
|
},
|
||||||
|
}
|
||||||
50
api/src/services/projects/projects.ts
Normal file
50
api/src/services/projects/projects.ts
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import type {
|
||||||
|
QueryResolvers,
|
||||||
|
MutationResolvers,
|
||||||
|
ProjectRelationResolvers,
|
||||||
|
} from 'types/graphql'
|
||||||
|
|
||||||
|
import { db } from 'src/lib/db'
|
||||||
|
|
||||||
|
export const projects: QueryResolvers['projects'] = () => {
|
||||||
|
return db.project.findMany()
|
||||||
|
}
|
||||||
|
|
||||||
|
export const project: QueryResolvers['project'] = ({ id }) => {
|
||||||
|
return db.project.findUnique({
|
||||||
|
where: { id },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createProject: MutationResolvers['createProject'] = ({
|
||||||
|
input,
|
||||||
|
}) => {
|
||||||
|
return db.project.create({
|
||||||
|
data: input,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const updateProject: MutationResolvers['updateProject'] = ({
|
||||||
|
id,
|
||||||
|
input,
|
||||||
|
}) => {
|
||||||
|
return db.project.update({
|
||||||
|
data: input,
|
||||||
|
where: { id },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const deleteProject: MutationResolvers['deleteProject'] = ({ id }) => {
|
||||||
|
return db.project.delete({
|
||||||
|
where: { id },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Project: ProjectRelationResolvers = {
|
||||||
|
images: (_obj, { root }) => {
|
||||||
|
return db.project.findUnique({ where: { id: root?.id } }).images()
|
||||||
|
},
|
||||||
|
tags: (_obj, { root }) => {
|
||||||
|
return db.project.findUnique({ where: { id: root?.id } }).tags()
|
||||||
|
},
|
||||||
|
}
|
||||||
42
api/src/services/tags/tags.ts
Normal file
42
api/src/services/tags/tags.ts
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import type {
|
||||||
|
QueryResolvers,
|
||||||
|
MutationResolvers,
|
||||||
|
TagRelationResolvers,
|
||||||
|
} from 'types/graphql'
|
||||||
|
|
||||||
|
import { db } from 'src/lib/db'
|
||||||
|
|
||||||
|
export const tags: QueryResolvers['tags'] = () => {
|
||||||
|
return db.tag.findMany()
|
||||||
|
}
|
||||||
|
|
||||||
|
export const tag: QueryResolvers['tag'] = ({ id }) => {
|
||||||
|
return db.tag.findUnique({
|
||||||
|
where: { id },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createTag: MutationResolvers['createTag'] = ({ input }) => {
|
||||||
|
return db.tag.create({
|
||||||
|
data: input,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const updateTag: MutationResolvers['updateTag'] = ({ id, input }) => {
|
||||||
|
return db.tag.update({
|
||||||
|
data: input,
|
||||||
|
where: { id },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const deleteTag: MutationResolvers['deleteTag'] = ({ id }) => {
|
||||||
|
return db.tag.delete({
|
||||||
|
where: { id },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Tag: TagRelationResolvers = {
|
||||||
|
projects: (_obj, { root }) => {
|
||||||
|
return db.tag.findUnique({ where: { id: root?.id } }).projects()
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -1,13 +1,12 @@
|
|||||||
import { FatalErrorBoundary, RedwoodProvider } from '@redwoodjs/web'
|
import { FatalErrorBoundary, RedwoodProvider } from '@redwoodjs/web'
|
||||||
import { RedwoodApolloProvider } from '@redwoodjs/web/apollo'
|
import { RedwoodApolloProvider } from '@redwoodjs/web/apollo'
|
||||||
|
|
||||||
|
import { AuthProvider, useAuth } from 'src/auth'
|
||||||
import FatalErrorPage from 'src/pages/FatalErrorPage'
|
import FatalErrorPage from 'src/pages/FatalErrorPage'
|
||||||
import Routes from 'src/Routes'
|
import Routes from 'src/Routes'
|
||||||
|
|
||||||
import './scaffold.css'
|
import 'src/scaffold.css'
|
||||||
import { AuthProvider, useAuth } from './auth'
|
import 'src/index.css'
|
||||||
|
|
||||||
import './index.css'
|
|
||||||
|
|
||||||
const App = () => (
|
const App = () => (
|
||||||
<FatalErrorBoundary page={FatalErrorPage}>
|
<FatalErrorBoundary page={FatalErrorPage}>
|
||||||
|
|||||||
@@ -1,24 +1,30 @@
|
|||||||
import { Router, Route, Set, PrivateSet } from '@redwoodjs/router'
|
import { Router, Route, Set, PrivateSet } from '@redwoodjs/router'
|
||||||
|
|
||||||
|
import { useAuth } from 'src/auth'
|
||||||
|
import AccountbarLayout from 'src/layouts/AccountbarLayout'
|
||||||
|
import NavbarLayout from 'src/layouts/NavbarLayout/NavbarLayout'
|
||||||
import ScaffoldLayout from 'src/layouts/ScaffoldLayout'
|
import ScaffoldLayout from 'src/layouts/ScaffoldLayout'
|
||||||
|
|
||||||
import { useAuth } from './auth'
|
|
||||||
import AccountbarLayout from './layouts/AccountbarLayout/AccountbarLayout'
|
|
||||||
import NavbarLayout from './layouts/NavbarLayout/NavbarLayout'
|
|
||||||
|
|
||||||
const Routes = () => {
|
const Routes = () => {
|
||||||
return (
|
return (
|
||||||
<Router useAuth={useAuth}>
|
<Router useAuth={useAuth}>
|
||||||
<PrivateSet unauthenticated="home">
|
<PrivateSet unauthenticated="home">
|
||||||
<Set wrap={ScaffoldLayout} title="Socials" titleTo="socials" buttonLabel="New Social" buttonTo="newSocial">
|
<Set wrap={ScaffoldLayout} title="Socials" titleTo="socials" buttonLabel="New Social" buttonTo="newSocial">
|
||||||
<Route path="/socials/new" page={SocialNewSocialPage} name="newSocial" />
|
<Route path="/admin/socials/new" page={SocialNewSocialPage} name="newSocial" />
|
||||||
<Route path="/socials/{id:Int}/edit" page={SocialEditSocialPage} name="editSocial" />
|
<Route path="/admin/socials/{id:Int}/edit" page={SocialEditSocialPage} name="editSocial" />
|
||||||
<Route path="/socials/{id:Int}" page={SocialSocialPage} name="social" />
|
<Route path="/admin/socials/{id:Int}" page={SocialSocialPage} name="social" />
|
||||||
<Route path="/socials" page={SocialSocialsPage} name="socials" />
|
<Route path="/admin/socials" page={SocialSocialsPage} name="socials" />
|
||||||
</Set>
|
</Set>
|
||||||
|
|
||||||
<Set wrap={ScaffoldLayout} title="Portrait" titleTo="portrait">
|
<Set wrap={ScaffoldLayout} title="Portrait" titleTo="portrait">
|
||||||
<Route path="/portrait" page={PortraitPortraitPage} name="portrait" />
|
<Route path="/admin/portrait" page={PortraitPortraitPage} name="portrait" />
|
||||||
|
</Set>
|
||||||
|
|
||||||
|
<Set wrap={ScaffoldLayout} title="Projects" titleTo="projects" buttonLabel="New Project" buttonTo="newProject">
|
||||||
|
<Route path="/admin/projects/new" page={ProjectNewProjectPage} name="newProject" />
|
||||||
|
<Route path="/admin/projects/{id:Int}/edit" page={ProjectEditProjectPage} name="editProject" />
|
||||||
|
<Route path="/admin/projects/{id:Int}" page={ProjectProjectPage} name="project" />
|
||||||
|
<Route path="/admin/projects" page={ProjectProjectsPage} name="projects" />
|
||||||
</Set>
|
</Set>
|
||||||
</PrivateSet>
|
</PrivateSet>
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export const QUERY: TypedDocumentNode<
|
|||||||
FindPortrait,
|
FindPortrait,
|
||||||
FindPortraitVariables
|
FindPortraitVariables
|
||||||
> = gql`
|
> = gql`
|
||||||
query FindPortrait {
|
query ContactCardPortrait {
|
||||||
portrait: portrait {
|
portrait: portrait {
|
||||||
fileId
|
fileId
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ import type {
|
|||||||
CreatePortraitMutationVariables,
|
CreatePortraitMutationVariables,
|
||||||
DeletePortraitMutation,
|
DeletePortraitMutation,
|
||||||
DeletePortraitMutationVariables,
|
DeletePortraitMutationVariables,
|
||||||
EditPortrait,
|
|
||||||
FindPortrait,
|
FindPortrait,
|
||||||
FindPortraitVariables,
|
FindPortraitVariables,
|
||||||
|
Portrait,
|
||||||
} from 'types/graphql'
|
} from 'types/graphql'
|
||||||
|
|
||||||
import { TypedDocumentNode, useMutation } from '@redwoodjs/web'
|
import { TypedDocumentNode, useMutation } from '@redwoodjs/web'
|
||||||
@@ -17,14 +17,14 @@ import { toast } from '@redwoodjs/web/dist/toast'
|
|||||||
import Uploader from 'src/components/Uploader/Uploader'
|
import Uploader from 'src/components/Uploader/Uploader'
|
||||||
|
|
||||||
interface PortraitFormProps {
|
interface PortraitFormProps {
|
||||||
portrait?: EditPortrait['portrait']
|
portrait?: Portrait
|
||||||
}
|
}
|
||||||
|
|
||||||
export const QUERY: TypedDocumentNode<
|
export const QUERY: TypedDocumentNode<
|
||||||
FindPortrait,
|
FindPortrait,
|
||||||
FindPortraitVariables
|
FindPortraitVariables
|
||||||
> = gql`
|
> = gql`
|
||||||
query FindPortrait {
|
query PortraitForm {
|
||||||
portrait {
|
portrait {
|
||||||
id
|
id
|
||||||
fileId
|
fileId
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
import type {
|
||||||
|
EditProjectById,
|
||||||
|
UpdateProjectInput,
|
||||||
|
UpdateProjectMutationVariables,
|
||||||
|
} from 'types/graphql'
|
||||||
|
|
||||||
|
import { navigate, routes } from '@redwoodjs/router'
|
||||||
|
import type {
|
||||||
|
CellSuccessProps,
|
||||||
|
CellFailureProps,
|
||||||
|
TypedDocumentNode,
|
||||||
|
} from '@redwoodjs/web'
|
||||||
|
import { useMutation } from '@redwoodjs/web'
|
||||||
|
import { toast } from '@redwoodjs/web/toast'
|
||||||
|
|
||||||
|
import ProjectForm from 'src/components/Project/ProjectForm'
|
||||||
|
|
||||||
|
export const QUERY: TypedDocumentNode<EditProjectById> = gql`
|
||||||
|
query EditProjectById($id: Int!) {
|
||||||
|
project: project(id: $id) {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
description
|
||||||
|
date
|
||||||
|
links
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const UPDATE_PROJECT_MUTATION: TypedDocumentNode<
|
||||||
|
EditProjectById,
|
||||||
|
UpdateProjectMutationVariables
|
||||||
|
> = gql`
|
||||||
|
mutation UpdateProjectMutation($id: Int!, $input: UpdateProjectInput!) {
|
||||||
|
updateProject(id: $id, input: $input) {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
description
|
||||||
|
date
|
||||||
|
links
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
export const Loading = () => <div>Loading...</div>
|
||||||
|
|
||||||
|
export const Failure = ({ error }: CellFailureProps) => (
|
||||||
|
<div className="rw-cell-error">{error?.message}</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
export const Success = ({ project }: CellSuccessProps<EditProjectById>) => {
|
||||||
|
const [updateProject, { loading, error }] = useMutation(
|
||||||
|
UPDATE_PROJECT_MUTATION,
|
||||||
|
{
|
||||||
|
onCompleted: () => {
|
||||||
|
toast.success('Project updated')
|
||||||
|
navigate(routes.projects())
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
toast.error(error.message)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const onSave = (
|
||||||
|
input: UpdateProjectInput,
|
||||||
|
id: EditProjectById['project']['id']
|
||||||
|
) => {
|
||||||
|
updateProject({ variables: { id, input } })
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="rw-segment">
|
||||||
|
<header className="rw-segment-header">
|
||||||
|
<h2 className="rw-heading rw-heading-secondary">
|
||||||
|
Edit Project {project?.id}
|
||||||
|
</h2>
|
||||||
|
</header>
|
||||||
|
<div className="rw-segment-main">
|
||||||
|
<ProjectForm
|
||||||
|
project={project}
|
||||||
|
onSave={onSave}
|
||||||
|
error={error}
|
||||||
|
loading={loading}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
55
web/src/components/Project/NewProject/NewProject.tsx
Normal file
55
web/src/components/Project/NewProject/NewProject.tsx
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import type {
|
||||||
|
CreateProjectMutation,
|
||||||
|
CreateProjectInput,
|
||||||
|
CreateProjectMutationVariables,
|
||||||
|
} from 'types/graphql'
|
||||||
|
|
||||||
|
import { navigate, routes } from '@redwoodjs/router'
|
||||||
|
import { useMutation } from '@redwoodjs/web'
|
||||||
|
import type { TypedDocumentNode } from '@redwoodjs/web'
|
||||||
|
import { toast } from '@redwoodjs/web/toast'
|
||||||
|
|
||||||
|
import ProjectForm from 'src/components/Project/ProjectForm'
|
||||||
|
|
||||||
|
const CREATE_PROJECT_MUTATION: TypedDocumentNode<
|
||||||
|
CreateProjectMutation,
|
||||||
|
CreateProjectMutationVariables
|
||||||
|
> = gql`
|
||||||
|
mutation CreateProjectMutation($input: CreateProjectInput!) {
|
||||||
|
createProject(input: $input) {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const NewProject = () => {
|
||||||
|
const [createProject, { loading, error }] = useMutation(
|
||||||
|
CREATE_PROJECT_MUTATION,
|
||||||
|
{
|
||||||
|
onCompleted: () => {
|
||||||
|
toast.success('Project created')
|
||||||
|
navigate(routes.projects())
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
toast.error(error.message)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const onSave = (input: CreateProjectInput) => {
|
||||||
|
createProject({ variables: { input } })
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="rw-segment">
|
||||||
|
<header className="rw-segment-header">
|
||||||
|
<h2 className="rw-heading rw-heading-secondary">New Project</h2>
|
||||||
|
</header>
|
||||||
|
<div className="rw-segment-main">
|
||||||
|
<ProjectForm onSave={onSave} loading={loading} error={error} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NewProject
|
||||||
98
web/src/components/Project/Project/Project.tsx
Normal file
98
web/src/components/Project/Project/Project.tsx
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
import type {
|
||||||
|
DeleteProjectMutation,
|
||||||
|
DeleteProjectMutationVariables,
|
||||||
|
FindProjectById,
|
||||||
|
} from 'types/graphql'
|
||||||
|
|
||||||
|
import { Link, routes, navigate } from '@redwoodjs/router'
|
||||||
|
import { useMutation } from '@redwoodjs/web'
|
||||||
|
import type { TypedDocumentNode } from '@redwoodjs/web'
|
||||||
|
import { toast } from '@redwoodjs/web/toast'
|
||||||
|
|
||||||
|
import { timeTag } from 'src/lib/formatters'
|
||||||
|
|
||||||
|
const DELETE_PROJECT_MUTATION: TypedDocumentNode<
|
||||||
|
DeleteProjectMutation,
|
||||||
|
DeleteProjectMutationVariables
|
||||||
|
> = gql`
|
||||||
|
mutation DeleteProjectMutation($id: Int!) {
|
||||||
|
deleteProject(id: $id) {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
project: NonNullable<FindProjectById['project']>
|
||||||
|
}
|
||||||
|
|
||||||
|
const Project = ({ project }: Props) => {
|
||||||
|
const [deleteProject] = useMutation(DELETE_PROJECT_MUTATION, {
|
||||||
|
onCompleted: () => {
|
||||||
|
toast.success('Project deleted')
|
||||||
|
navigate(routes.projects())
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
toast.error(error.message)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const onDeleteClick = (id: DeleteProjectMutationVariables['id']) => {
|
||||||
|
if (confirm('Are you sure you want to delete project ' + id + '?')) {
|
||||||
|
deleteProject({ variables: { id } })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="rw-segment">
|
||||||
|
<header className="rw-segment-header">
|
||||||
|
<h2 className="rw-heading rw-heading-secondary">
|
||||||
|
Project {project.id} Detail
|
||||||
|
</h2>
|
||||||
|
</header>
|
||||||
|
<table className="rw-table">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th>Id</th>
|
||||||
|
<td>{project.id}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Title</th>
|
||||||
|
<td>{project.title}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Description</th>
|
||||||
|
<td>{project.description}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Date</th>
|
||||||
|
<td>{timeTag(project.date)}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Links</th>
|
||||||
|
<td>{project.links}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<nav className="rw-button-group">
|
||||||
|
<Link
|
||||||
|
to={routes.editProject({ id: project.id })}
|
||||||
|
className="rw-button rw-button-blue"
|
||||||
|
>
|
||||||
|
Edit
|
||||||
|
</Link>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="rw-button rw-button-red"
|
||||||
|
onClick={() => onDeleteClick(project.id)}
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</button>
|
||||||
|
</nav>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Project
|
||||||
40
web/src/components/Project/ProjectCell/ProjectCell.tsx
Normal file
40
web/src/components/Project/ProjectCell/ProjectCell.tsx
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import type { FindProjectById, FindProjectByIdVariables } from 'types/graphql'
|
||||||
|
|
||||||
|
import type {
|
||||||
|
CellSuccessProps,
|
||||||
|
CellFailureProps,
|
||||||
|
TypedDocumentNode,
|
||||||
|
} from '@redwoodjs/web'
|
||||||
|
|
||||||
|
import Project from 'src/components/Project/Project'
|
||||||
|
|
||||||
|
export const QUERY: TypedDocumentNode<
|
||||||
|
FindProjectById,
|
||||||
|
FindProjectByIdVariables
|
||||||
|
> = gql`
|
||||||
|
query FindProjectById($id: Int!) {
|
||||||
|
project: project(id: $id) {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
description
|
||||||
|
date
|
||||||
|
links
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
export const Loading = () => <div>Loading...</div>
|
||||||
|
|
||||||
|
export const Empty = () => <div>Project not found</div>
|
||||||
|
|
||||||
|
export const Failure = ({
|
||||||
|
error,
|
||||||
|
}: CellFailureProps<FindProjectByIdVariables>) => (
|
||||||
|
<div className="rw-cell-error">{error?.message}</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
export const Success = ({
|
||||||
|
project,
|
||||||
|
}: CellSuccessProps<FindProjectById, FindProjectByIdVariables>) => {
|
||||||
|
return <Project project={project} />
|
||||||
|
}
|
||||||
101
web/src/components/Project/ProjectForm/ProjectForm.tsx
Normal file
101
web/src/components/Project/ProjectForm/ProjectForm.tsx
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
import type { EditProjectById, UpdateProjectInput } from 'types/graphql'
|
||||||
|
|
||||||
|
import type { RWGqlError } from '@redwoodjs/forms'
|
||||||
|
import {
|
||||||
|
Form,
|
||||||
|
FormError,
|
||||||
|
FieldError,
|
||||||
|
Label,
|
||||||
|
TextField,
|
||||||
|
Submit,
|
||||||
|
} from '@redwoodjs/forms'
|
||||||
|
|
||||||
|
type FormProject = NonNullable<EditProjectById['project']>
|
||||||
|
|
||||||
|
interface ProjectFormProps {
|
||||||
|
project?: EditProjectById['project']
|
||||||
|
onSave: (data: UpdateProjectInput, id?: FormProject['id']) => void
|
||||||
|
error: RWGqlError
|
||||||
|
loading: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const ProjectForm = (props: ProjectFormProps) => {
|
||||||
|
const onSubmit = (data: FormProject) => {
|
||||||
|
props.onSave(data, props?.project?.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="rw-form-wrapper">
|
||||||
|
<Form<FormProject> onSubmit={onSubmit} error={props.error}>
|
||||||
|
<FormError
|
||||||
|
error={props.error}
|
||||||
|
wrapperClassName="rw-form-error-wrapper"
|
||||||
|
titleClassName="rw-form-error-title"
|
||||||
|
listClassName="rw-form-error-list"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Label
|
||||||
|
name="title"
|
||||||
|
className="rw-label"
|
||||||
|
errorClassName="rw-label rw-label-error"
|
||||||
|
>
|
||||||
|
Title
|
||||||
|
</Label>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
name="title"
|
||||||
|
defaultValue={props.project?.title}
|
||||||
|
className="rw-input"
|
||||||
|
errorClassName="rw-input rw-input-error"
|
||||||
|
validation={{ required: true }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FieldError name="title" className="rw-field-error" />
|
||||||
|
|
||||||
|
<Label
|
||||||
|
name="description"
|
||||||
|
className="rw-label"
|
||||||
|
errorClassName="rw-label rw-label-error"
|
||||||
|
>
|
||||||
|
Description
|
||||||
|
</Label>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
name="description"
|
||||||
|
defaultValue={props.project?.description}
|
||||||
|
className="rw-input"
|
||||||
|
errorClassName="rw-input rw-input-error"
|
||||||
|
validation={{ required: true }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FieldError name="description" className="rw-field-error" />
|
||||||
|
|
||||||
|
<Label
|
||||||
|
name="links"
|
||||||
|
className="rw-label"
|
||||||
|
errorClassName="rw-label rw-label-error"
|
||||||
|
>
|
||||||
|
Links
|
||||||
|
</Label>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
name="links"
|
||||||
|
defaultValue={props.project?.links}
|
||||||
|
className="rw-input"
|
||||||
|
errorClassName="rw-input rw-input-error"
|
||||||
|
validation={{ required: true }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FieldError name="links" className="rw-field-error" />
|
||||||
|
|
||||||
|
<div className="rw-button-group">
|
||||||
|
<Submit disabled={props.loading} className="rw-button rw-button-blue">
|
||||||
|
Save
|
||||||
|
</Submit>
|
||||||
|
</div>
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ProjectForm
|
||||||
102
web/src/components/Project/Projects/Projects.tsx
Normal file
102
web/src/components/Project/Projects/Projects.tsx
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
import type {
|
||||||
|
DeleteProjectMutation,
|
||||||
|
DeleteProjectMutationVariables,
|
||||||
|
FindProjects,
|
||||||
|
} from 'types/graphql'
|
||||||
|
|
||||||
|
import { Link, routes } from '@redwoodjs/router'
|
||||||
|
import { useMutation } from '@redwoodjs/web'
|
||||||
|
import type { TypedDocumentNode } from '@redwoodjs/web'
|
||||||
|
import { toast } from '@redwoodjs/web/toast'
|
||||||
|
|
||||||
|
import { QUERY } from 'src/components/Project/ProjectsCell'
|
||||||
|
import { timeTag, truncate } from 'src/lib/formatters'
|
||||||
|
|
||||||
|
const DELETE_PROJECT_MUTATION: TypedDocumentNode<
|
||||||
|
DeleteProjectMutation,
|
||||||
|
DeleteProjectMutationVariables
|
||||||
|
> = gql`
|
||||||
|
mutation DeleteProjectMutation($id: Int!) {
|
||||||
|
deleteProject(id: $id) {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const ProjectsList = ({ projects }: FindProjects) => {
|
||||||
|
const [deleteProject] = useMutation(DELETE_PROJECT_MUTATION, {
|
||||||
|
onCompleted: () => {
|
||||||
|
toast.success('Project deleted')
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
toast.error(error.message)
|
||||||
|
},
|
||||||
|
// This refetches the query on the list page. Read more about other ways to
|
||||||
|
// update the cache over here:
|
||||||
|
// https://www.apollographql.com/docs/react/data/mutations/#making-all-other-cache-updates
|
||||||
|
refetchQueries: [{ query: QUERY }],
|
||||||
|
awaitRefetchQueries: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
const onDeleteClick = (id: DeleteProjectMutationVariables['id']) => {
|
||||||
|
if (confirm('Are you sure you want to delete project ' + id + '?')) {
|
||||||
|
deleteProject({ variables: { id } })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="rw-segment rw-table-wrapper-responsive">
|
||||||
|
<table className="rw-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Id</th>
|
||||||
|
<th>Title</th>
|
||||||
|
<th>Description</th>
|
||||||
|
<th>Date</th>
|
||||||
|
<th>Links</th>
|
||||||
|
<th> </th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{projects.map((project) => (
|
||||||
|
<tr key={project.id}>
|
||||||
|
<td>{truncate(project.id)}</td>
|
||||||
|
<td>{truncate(project.title)}</td>
|
||||||
|
<td>{truncate(project.description)}</td>
|
||||||
|
<td>{timeTag(project.date)}</td>
|
||||||
|
<td>{truncate(project.links)}</td>
|
||||||
|
<td>
|
||||||
|
<nav className="rw-table-actions">
|
||||||
|
<Link
|
||||||
|
to={routes.project({ id: project.id })}
|
||||||
|
title={'Show project ' + project.id + ' detail'}
|
||||||
|
className="rw-button rw-button-small"
|
||||||
|
>
|
||||||
|
Show
|
||||||
|
</Link>
|
||||||
|
<Link
|
||||||
|
to={routes.editProject({ id: project.id })}
|
||||||
|
title={'Edit project ' + project.id}
|
||||||
|
className="rw-button rw-button-small rw-button-blue"
|
||||||
|
>
|
||||||
|
Edit
|
||||||
|
</Link>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
title={'Delete project ' + project.id}
|
||||||
|
className="rw-button rw-button-small rw-button-red"
|
||||||
|
onClick={() => onDeleteClick(project.id)}
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</button>
|
||||||
|
</nav>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ProjectsList
|
||||||
48
web/src/components/Project/ProjectsCell/ProjectsCell.tsx
Normal file
48
web/src/components/Project/ProjectsCell/ProjectsCell.tsx
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import type { FindProjects, FindProjectsVariables } from 'types/graphql'
|
||||||
|
|
||||||
|
import { Link, routes } from '@redwoodjs/router'
|
||||||
|
import type {
|
||||||
|
CellSuccessProps,
|
||||||
|
CellFailureProps,
|
||||||
|
TypedDocumentNode,
|
||||||
|
} from '@redwoodjs/web'
|
||||||
|
|
||||||
|
import Projects from 'src/components/Project/Projects'
|
||||||
|
|
||||||
|
export const QUERY: TypedDocumentNode<
|
||||||
|
FindProjects,
|
||||||
|
FindProjectsVariables
|
||||||
|
> = gql`
|
||||||
|
query FindProjects {
|
||||||
|
projects {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
description
|
||||||
|
date
|
||||||
|
links
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
export const Loading = () => <div>Loading...</div>
|
||||||
|
|
||||||
|
export const Empty = () => {
|
||||||
|
return (
|
||||||
|
<div className="rw-text-center">
|
||||||
|
{'No projects yet. '}
|
||||||
|
<Link to={routes.newProject()} className="rw-link">
|
||||||
|
{'Create one?'}
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Failure = ({ error }: CellFailureProps<FindProjects>) => (
|
||||||
|
<div className="rw-cell-error">{error?.message}</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
export const Success = ({
|
||||||
|
projects,
|
||||||
|
}: CellSuccessProps<FindProjects, FindProjectsVariables>) => {
|
||||||
|
return <Projects projects={projects} />
|
||||||
|
}
|
||||||
@@ -1,6 +1,13 @@
|
|||||||
import { useEffect, useRef, useState } from 'react'
|
import { useEffect, useRef, useState } from 'react'
|
||||||
|
|
||||||
import { mdiMenuUp, mdiMenuDown, mdiAccount, mdiRename } from '@mdi/js'
|
import {
|
||||||
|
mdiMenuUp,
|
||||||
|
mdiMenuDown,
|
||||||
|
mdiAccount,
|
||||||
|
mdiRename,
|
||||||
|
mdiAt,
|
||||||
|
mdiLinkVariant,
|
||||||
|
} from '@mdi/js'
|
||||||
import Icon from '@mdi/react'
|
import Icon from '@mdi/react'
|
||||||
import type { EditSocialById, UpdateSocialInput } from 'types/graphql'
|
import type { EditSocialById, UpdateSocialInput } from 'types/graphql'
|
||||||
|
|
||||||
@@ -110,10 +117,18 @@ const SocialForm = (props: SocialFormProps) => {
|
|||||||
>
|
>
|
||||||
<Label
|
<Label
|
||||||
name="username"
|
name="username"
|
||||||
className="h-4 w-4 opacity-70"
|
className="size-5 opacity-70"
|
||||||
errorClassName="h-4 w-4 text-error"
|
errorClassName="size-5 text-error"
|
||||||
>
|
>
|
||||||
<Icon path={mdiAccount} />
|
<Icon
|
||||||
|
path={
|
||||||
|
type == 'email'
|
||||||
|
? mdiAt
|
||||||
|
: type == 'custom'
|
||||||
|
? mdiLinkVariant
|
||||||
|
: mdiAccount
|
||||||
|
}
|
||||||
|
/>
|
||||||
</Label>
|
</Label>
|
||||||
<TextField
|
<TextField
|
||||||
name="username"
|
name="username"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Toaster } from '@redwoodjs/web/dist/toast'
|
import { Toaster } from '@redwoodjs/web/dist/toast'
|
||||||
|
|
||||||
import ToastNotification from '../ToastNotification/ToastNotification'
|
import ToastNotification from 'src/components/ToastNotification'
|
||||||
|
|
||||||
const ToasterWrapper = () => (
|
const ToasterWrapper = () => (
|
||||||
<Toaster
|
<Toaster
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { hydrateRoot, createRoot } from 'react-dom/client'
|
import { hydrateRoot, createRoot } from 'react-dom/client'
|
||||||
|
|
||||||
import App from './App'
|
import App from 'src/App'
|
||||||
/**
|
/**
|
||||||
* When `#redwood-app` isn't empty then it's very likely that you're using
|
* When `#redwood-app` isn't empty then it's very likely that you're using
|
||||||
* prerendering. So React attaches event listeners to the existing markup
|
* prerendering. So React attaches event listeners to the existing markup
|
||||||
|
|||||||
@@ -1,16 +1,9 @@
|
|||||||
import { Metadata } from '@redwoodjs/web'
|
import { Metadata } from '@redwoodjs/web'
|
||||||
|
|
||||||
import { useAuth } from 'src/auth'
|
|
||||||
import Uploader from 'src/components/Uploader/Uploader'
|
|
||||||
|
|
||||||
const HomePage = () => {
|
const HomePage = () => {
|
||||||
const { isAuthenticated } = useAuth()
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Metadata title="Home" description="Home page" />
|
<Metadata title="Home" description="Home page" />
|
||||||
|
|
||||||
{isAuthenticated && <Uploader />}
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
11
web/src/pages/Project/EditProjectPage/EditProjectPage.tsx
Normal file
11
web/src/pages/Project/EditProjectPage/EditProjectPage.tsx
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import EditProjectCell from 'src/components/Project/EditProjectCell'
|
||||||
|
|
||||||
|
type ProjectPageProps = {
|
||||||
|
id: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const EditProjectPage = ({ id }: ProjectPageProps) => {
|
||||||
|
return <EditProjectCell id={id} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EditProjectPage
|
||||||
7
web/src/pages/Project/NewProjectPage/NewProjectPage.tsx
Normal file
7
web/src/pages/Project/NewProjectPage/NewProjectPage.tsx
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import NewProject from 'src/components/Project/NewProject'
|
||||||
|
|
||||||
|
const NewProjectPage = () => {
|
||||||
|
return <NewProject />
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NewProjectPage
|
||||||
11
web/src/pages/Project/ProjectPage/ProjectPage.tsx
Normal file
11
web/src/pages/Project/ProjectPage/ProjectPage.tsx
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import ProjectCell from 'src/components/Project/ProjectCell'
|
||||||
|
|
||||||
|
type ProjectPageProps = {
|
||||||
|
id: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const ProjectPage = ({ id }: ProjectPageProps) => {
|
||||||
|
return <ProjectCell id={id} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ProjectPage
|
||||||
7
web/src/pages/Project/ProjectsPage/ProjectsPage.tsx
Normal file
7
web/src/pages/Project/ProjectsPage/ProjectsPage.tsx
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import ProjectsCell from 'src/components/Project/ProjectsCell'
|
||||||
|
|
||||||
|
const ProjectsPage = () => {
|
||||||
|
return <ProjectsCell />
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ProjectsPage
|
||||||
@@ -7280,6 +7280,7 @@ __metadata:
|
|||||||
"@tus/file-store": "npm:^1.4.0"
|
"@tus/file-store": "npm:^1.4.0"
|
||||||
"@tus/server": "npm:^1.7.0"
|
"@tus/server": "npm:^1.7.0"
|
||||||
"@types/nodemailer": "npm:^6.4.15"
|
"@types/nodemailer": "npm:^6.4.15"
|
||||||
|
graphql-scalars: "npm:^1.23.0"
|
||||||
nodemailer: "npm:^6.9.14"
|
nodemailer: "npm:^6.9.14"
|
||||||
languageName: unknown
|
languageName: unknown
|
||||||
linkType: soft
|
linkType: soft
|
||||||
@@ -12683,7 +12684,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"graphql-scalars@npm:1.23.0":
|
"graphql-scalars@npm:1.23.0, graphql-scalars@npm:^1.23.0":
|
||||||
version: 1.23.0
|
version: 1.23.0
|
||||||
resolution: "graphql-scalars@npm:1.23.0"
|
resolution: "graphql-scalars@npm:1.23.0"
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|||||||
Reference in New Issue
Block a user