Conversation
web/pages/ProjectsPage.tsx
Outdated
|
|
||
| export function ProjectsPage() { | ||
| const prodOpen = useComputed(() => url.params.prodopen !== 'false') | ||
| const devOpen = useComputed(() => url.params.devopen !== 'false') |
There was a problem hiding this comment.
const devOpen = url.params.devopen !== 'false'f31b8d2 to
5b2e731
Compare
6bf2103 to
6c7d253
Compare
…enhanced dialogs and improved data handling
6c7d253 to
8771202
Compare
There was a problem hiding this comment.
Pull Request Overview
This PR adds a comprehensive Projects page with team and project management functionality to a Preact application using DaisyUI components. The implementation includes modal dialogs for CRUD operations, user interface improvements, and database schema updates.
- Introduces ProjectsPage component with search, filtering, and management capabilities
- Adds modal dialogs for project creation, team management, and deletion confirmation
- Updates user schema to include admin privileges and authentication flow
Reviewed Changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| web/pages/ProjectsPage.tsx | New comprehensive projects page with team/project management, search functionality, and modal dialogs |
| web/index.tsx | Updates main app to render ProjectsPage instead of welcome message |
| web/components/Dialog.tsx | New reusable dialog component with modal functionality |
| api/user.ts | Sets new users as non-admin by default during OAuth authentication |
| api/schema.ts | Adds isAdmin boolean field to user schema |
web/pages/ProjectsPage.tsx
Outdated
| type='button' | ||
| onClick={() => openDialog('add-project')} | ||
| class='btn btn-primary btn-sm flex items-center gap-1.5' | ||
| disabled={teams.value.length > 0 && !user.data?.isAdmin} |
There was a problem hiding this comment.
The logic for disabling the 'Add Project' button is incorrect. It should be disabled when there are no teams OR when the user is not an admin. The current condition teams.value.length > 0 && !user.data?.isAdmin will disable the button only when there are teams AND the user is not an admin, but it should also be disabled when there are no teams at all.
| disabled={teams.value.length > 0 && !user.data?.isAdmin} | |
| disabled={teams.value.length === 0 || !user.data?.isAdmin} |
web/pages/ProjectsPage.tsx
Outdated
|
|
||
| // valeurs initiales sans useEffect | ||
| const name = useSignal(project?.name ?? '') | ||
| const teamId = useSignal<number | null>(project?.teamId ?? null) //user.data?.teamIds[0] ?? null |
There was a problem hiding this comment.
Remove the commented code //user.data?.teamIds[0] ?? null as it appears to be leftover development code and adds no value to the implementation.
| const teamId = useSignal<number | null>(project?.teamId ?? null) //user.data?.teamIds[0] ?? null | |
| const teamId = useSignal<number | null>(project?.teamId ?? null) |
web/pages/ProjectsPage.tsx
Outdated
| const authorized = true | ||
|
|
||
| return ( |
There was a problem hiding this comment.
The authorized variable is hardcoded to true but appears to be intended for actual authorization logic. This should either implement proper authorization checking or be removed if not needed.
| const authorized = true | |
| return ( | |
| // User is authorized if they are an admin or belong to the project's team | |
| const authorized = | |
| user.value?.isAdmin || | |
| (user.value?.teamIds && user.value.teamIds.includes(project.teamId)); |
web/pages/ProjectsPage.tsx
Outdated
|
|
||
| function deleteTeam(id: number) { | ||
| if (projects.value.some((p) => p.teamId === id)) { | ||
| toast('Cannot delete a team that still has projects.') |
There was a problem hiding this comment.
The toast function is called but not defined in scope. It should be imported or the function call should be qualified with the module it belongs to.
| @@ -1,11 +1,12 @@ | |||
| import { OBJ, optional, STR } from './lib/validator.ts' | |||
| import { BOOL, OBJ, optional, STR } from './lib/validator.ts' | |||
| import { Asserted } from './lib/router.ts' | |||
There was a problem hiding this comment.
deno run -A https://gistcdn.githack.com/kigiri/21df06d173fcdced5281b86ba6ac1382/raw/mod.ts
Past your secret: past your env
Input your password: ...
> f936ee830f54fb12e8376585c05a9df00ca12c438cb983in code:
import { decrypt } from 'https://gistcdn.githack.com/kigiri/21df06d173fcdced5281b86ba6ac1382/raw/crypto.js'
const env = await decrypt(
'f936ee830f54fb12e8376585c05a9df00ca12c438cb983',
localStorage.password || (localStorage.password = prompt('password)),
)
await Deno.writeTextFile('./env.dev', env)
web/pages/ProjectsPage.tsx
Outdated
| {/* Sidebar */} | ||
| <aside class='w-72 shrink-0 border-r border-divider flex flex-col'> | ||
| <div class='p-4 flex-1 flex flex-col'> | ||
| <h3 class='text-sm font-medium text-text2 mb-3'>Teams</h3> |
There was a problem hiding this comment.
const DialogSectionTitle = (props: JSX.props...) =>
<h3 class='text-sm font-medium text-text2 mb-3' {...props} />
web/pages/ProjectsPage.tsx
Outdated
| if (idx === -1) projects.value = [...projects.value, project] | ||
| else projects.value[idx] = { ...projects.value[idx], ...project } | ||
|
|
||
| closeDialog() |
There was a problem hiding this comment.
const projectsValues = projects.peek()
if (!slug) {
const base = slugify(name)
let suffix = ''
do {
finalSlug = base + suffix
slug = suffix ? String(Number(suffix) + 1) : '-1'
} while (projectsValues.some((p) => p.slug === slug))
const now = new Date().toISOString()
const project: Project = { slug, name, teamId, createdAt: now }
projects.value = [...projectsValues, project]
} else {
const idx = projectsValues.findIndex((p) => p.slug === slug)
const copy = [...projectsValues]
copy[idx] = { ...projectsValues[idx], name, teamId }
project.values = copy
}
closeDialog()a97bac6 to
b16a336
Compare
…ontent, and form fields
b16a336 to
09a1d7c
Compare
web/pages/ProjectsPage.tsx
Outdated
| <div class='flex items-center gap-1 flex-shrink-0'> | ||
| <Calendar class='w-3.5 h-3.5' /> | ||
| <span> | ||
| {new Date(project.createdAt).toLocaleDateString('fr-CA')} |
There was a problem hiding this comment.
better not specify and let browser default
| onInput={(e) => (name.value = (e.target as HTMLInputElement).value)} | ||
| required | ||
| class='input input-bordered w-full' | ||
| /> |
There was a problem hiding this comment.
<input
name='Name'
type='text'
defaultValue={project?.name ?? ''}
required
class='input input-bordered w-full'
/>
web/pages/ProjectsPage.tsx
Outdated
| e.preventDefault() | ||
| if (!teamId.value) return | ||
| saveProject({ | ||
| name: name.value, |
There was a problem hiding this comment.
// get value from input[name="Name"]
web/pages/ProjectsPage.tsx
Outdated
| ? projects.value.find((p) => p.slug === slug) | ||
| : undefined | ||
|
|
||
| const name = useSignal(project?.name ?? '') |
| )} | ||
| {tab.value === 'settings' && ( | ||
| <TeamSettingsSection team={selectedTeam} /> | ||
| )} |
| navigate({ | ||
| params: { q: (e.target as HTMLInputElement).value || null }, | ||
| replace: true, | ||
| }) |
There was a problem hiding this comment.
this function never change, no need to redefine it on every render of ProjectPage
aebaee2 to
2837314
Compare
2837314 to
f5b80f3
Compare
fead(ProjectsPage.tsx): Add projects page with modal and card components