Skip to content

Commit

Permalink
Merge pull request #19 from harley-codes/task/create-project-page
Browse files Browse the repository at this point in the history
Create project page
  • Loading branch information
harley-codes authored Mar 18, 2024
2 parents 067d8d6 + 0e8b6f5 commit e984ea2
Show file tree
Hide file tree
Showing 35 changed files with 1,150 additions and 116 deletions.
3 changes: 2 additions & 1 deletion .vscode/extensions.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"streetsidesoftware.code-spell-checker",
"github.vscode-github-actions",
"wix.vscode-import-cost",
"prisma.prisma"
"prisma.prisma",
"gruntfuggly.todo-tree"
]
}
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
"bunx",
"fontsource",
"linq",
"NEXTAUTH"
"NEXTAUTH",
"preact"
],
"typescript.tsdk": "node_modules/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true,
Expand Down
Binary file modified bun.lockb
Binary file not shown.
5 changes: 5 additions & 0 deletions next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ const nextConfig = {
sassOptions: {
includePaths: [path.join(__dirname, 'src', 'styles')],
},
experimental: {
// TODO: Followup and make sure all security concerns are addressed.
// https://nextjs.org/docs/app/building-your-application/data-fetching/forms-and-mutations#convention
serverActions: true,
},
}

module.exports = nextConfig
73 changes: 38 additions & 35 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,37 +1,40 @@
{
"name": "hobby-cms",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"dev:t": "next dev --turbo",
"build": "next build",
"start": "next start",
"lint": "next lint",
"lint:fix": "next lint --fix",
"db:prisma-cockroach:generate": "prisma generate --schema=src/modules/database/vendors/prisma-cockroach/schema.prisma",
"db:prisma-cockroach:migrate": "prisma migrate dev --name migrate --schema=src/modules/database/vendors/prisma-cockroach/schema.prisma"
},
"dependencies": {
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
"@fontsource/roboto": "^5.0.8",
"@mui/icons-material": "^5.14.13",
"@mui/material": "^5.14.14",
"@prisma/client": "5.7.1",
"next": "13.5.6",
"next-auth": "^4.24.3",
"react": "^18",
"react-dom": "^18",
"sass": "^1.69.5"
},
"devDependencies": {
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"eslint": "^8",
"eslint-config-next": "13.5.6",
"typescript": "^5",
"prisma": "^5.7.1"
}
"name": "hobby-cms",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"dev:t": "next dev --turbo",
"build": "next build",
"start": "next start",
"lint": "next lint",
"lint:fix": "next lint --fix",
"db:prisma-cockroach:generate": "prisma generate --schema=src/modules/database/vendors/prisma-cockroach/schema.prisma",
"db:prisma-cockroach:migrate": "prisma migrate dev --name migrate --schema=src/modules/database/vendors/prisma-cockroach/schema.prisma"
},
"dependencies": {
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
"@fontsource/roboto": "^5.0.8",
"@mui/icons-material": "^5.14.13",
"@mui/material": "^5.14.14",
"@prisma/client": "5.7.1",
"@types/uuid": "^9.0.8",
"linq": "^4.0.2",
"next": "13.5.6",
"next-auth": "^4.24.3",
"react": "^18",
"react-dom": "^18",
"sass": "^1.69.5",
"uuid": "^9.0.1"
},
"devDependencies": {
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"eslint": "^8",
"eslint-config-next": "13.5.6",
"typescript": "^5",
"prisma": "^5.7.1"
}
}
10 changes: 0 additions & 10 deletions src/app/(admin)/layout.tsx

This file was deleted.

23 changes: 0 additions & 23 deletions src/app/(admin)/nextauth/page.tsx

This file was deleted.

6 changes: 3 additions & 3 deletions src/app/dashboard/AppMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ import { useState } from 'react'

const navLinks = [
{ title: 'Dashboard', path: '/dashboard' },
{ title: 'Projects', path: '/projects' },
{ title: 'Posts', path: '/posts' },
{ title: 'Images', path: '/images' },
{ title: 'Projects', path: '/dashboard/projects' },
{ title: 'Posts', path: '/dashboard/posts' },
{ title: 'Images', path: '/dashboard/images' },
]

type MenuButtonProps = { title: string, path?: string, action?: () => void }
Expand Down
17 changes: 17 additions & 0 deletions src/app/dashboard/_actions/accessTokenActions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
'use server'

import { getDatabaseClientAsync } from '@/modules/database/databaseFactory'
import { AccessTokenDetail } from '@/modules/database/responseTypes'

export async function createProjectTokenServerAction(projectId: string): Promise<AccessTokenDetail>
{
const client = await getDatabaseClientAsync()
const token = await client.createAccessTokenAsync(projectId)
return token
}

export async function deleteProjectTokenServerAction(tokenId: string): Promise<void>
{
const client = await getDatabaseClientAsync()
await client.deleteAccessTokenAsync(tokenId)
}
25 changes: 25 additions & 0 deletions src/app/dashboard/_actions/projectActions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
'use server'

import { getDatabaseClientAsync } from '@/modules/database/databaseFactory'
import { ProjectUpdateValues } from '@/modules/database/requestTypes'
import { ProjectDetail } from '@/modules/database/responseTypes'

export async function createProjectServerAction(projectName: string, isActive: boolean): Promise<ProjectDetail>
{
const client = await getDatabaseClientAsync()
const project = await client.createProjectAsync(projectName, isActive)
return project
}

export async function deleteProjectServerAction(projectId: string): Promise<void>
{
const client = await getDatabaseClientAsync()
await client.deleteProjectAsync(projectId)
}

export async function updateProjectServerAction(projectId: string, values: ProjectUpdateValues): Promise<ProjectDetail>
{
const client = await getDatabaseClientAsync()
const project = await client.updateProjectAsync(projectId, values)
return project
}
5 changes: 3 additions & 2 deletions src/app/dashboard/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import AppMenu from '@/app/dashboard/AppMenu'
import { RequireSessionWrapper } from '@/modules/auth/RequireSessionWrapper'
import { Container } from '@mui/material'

export default function Layout(props: ChildProps)
{
return (
<RequireSessionWrapper>
<AppMenu />
<main>
<Container maxWidth="lg" component="main">
{props.children}
</main>
</Container>
</RequireSessionWrapper>
)
}
3 changes: 2 additions & 1 deletion src/app/dashboard/page.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { Typography } from '@mui/material'

export default function DashboardPage()
{
return (
<>
<h1>Dashboard</h1>
<Typography variant="h4">Dashboard</Typography>
</>
)
}
11 changes: 11 additions & 0 deletions src/app/dashboard/projects/_components/CreateProjectButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
'use client'

import { invokeNewProjectRequest } from '@/app/dashboard/projects/_components/CreateProjectDialog'
import { Button } from '@mui/material'

export function CreateProjectButton()
{
return (
<Button onClick={() => invokeNewProjectRequest()}>Create Project</Button>
)
}
98 changes: 98 additions & 0 deletions src/app/dashboard/projects/_components/CreateProjectDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
'use client'

import { invokeConfirmationModal } from '@/components/ConfirmationModal'
import { createEvent } from '@/modules/custom-events/createEvent'
import { Button, Dialog, DialogActions, DialogContent, DialogTitle, TextField } from '@mui/material'
import { useState } from 'react'

type Props = {
currentProjectNames: string[]
onCreateProject: (name: string) => void
}

const newProjectRequestEvent = createEvent<null>('newProjectRequest')

export const invokeNewProjectRequest = () => newProjectRequestEvent.callEvent(null)

const defaultState = {
display: false,
name: '',
nameInUse: false,
process: false
}

export function CreateProjectDialog(props: Props)
{
const { currentProjectNames, onCreateProject } = props

const [state, setState] = useState(defaultState)

newProjectRequestEvent.useEvent(() =>
{
setState({ ...defaultState, display: true })
})

function setNameHandler(e: React.ChangeEvent<HTMLInputElement>)
{
setState({
...state,
name: e.currentTarget.value,
nameInUse: currentProjectNames.includes(e.currentTarget.value)
})
}

function cancelHandler()
{
setState({ ...state, display: false, process: false })
}

function createStartHandler()
{
if (state.nameInUse) return

invokeConfirmationModal({
description: `Are you sure you want to create a project named "${state.name}"?`,
onConfirmed: (confirmed) =>
{
if (confirmed)
{
setState({ ...state, display: false, process: true })
}
}
})
}

function exitHandler()
{
if (state.process) onCreateProject(state.name)
setState(defaultState)
}

return (
<Dialog
open={state.display}
TransitionProps={{
onExited: exitHandler,
}}
fullWidth maxWidth="xs"
>
<DialogTitle>Create Project</DialogTitle>
<DialogContent>
<TextField
label="Project Name"
margin="normal"
fullWidth
value={state.name}
onChange={setNameHandler}
error={state.nameInUse}
helperText={state.nameInUse ? 'Name already in use' : ' '}
/>

</DialogContent>
<DialogActions>
<Button onClick={cancelHandler}>Cancel</Button>
<Button onClick={createStartHandler} disabled={state.nameInUse}>Create</Button>
</DialogActions>
</Dialog>
)
}
Loading

0 comments on commit e984ea2

Please sign in to comment.