Skip to content

Commit

Permalink
fix: change add route for software, project and news to solve add slu…
Browse files Browse the repository at this point in the history
…g problem.
  • Loading branch information
dmijatovic committed Nov 11, 2024
1 parent af03d08 commit 2da89e3
Show file tree
Hide file tree
Showing 12 changed files with 133 additions and 128 deletions.
2 changes: 1 addition & 1 deletion e2e/helpers/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export async function createProject({title, desc, slug, page}: CreateSoftwarePro
await addMenu.click()
// click new project
await Promise.all([
page.waitForURL('**/projects/add', {
page.waitForURL('**/add/project', {
waitUntil: 'networkidle'
}),
newProject.click()
Expand Down
2 changes: 1 addition & 1 deletion e2e/helpers/software.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export async function createSoftware({title, desc, slug, page}: CreateSoftwarePr
await addMenu.click()
// open add software page
await Promise.all([
page.waitForURL('**/software/add',{
page.waitForURL('**/add/software',{
waitUntil: 'networkidle'
}),
newSoftware.click()
Expand Down
6 changes: 3 additions & 3 deletions frontend/components/AppHeader/AddMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,14 +75,14 @@ export default function AddMenu() {
// disable adding styles to body (overflow:hidden & padding-right)
disableScrollLock={disable}
>
<MenuItem data-testid="add-menu-option" onClick={() => handleClose('/software/add')}>
<MenuItem data-testid="add-menu-option" onClick={() => handleClose('/add/software')}>
<ListItemIcon>
<TerminalIcon/>
</ListItemIcon>
New Software
</MenuItem>

<MenuItem data-testid="add-menu-option" onClick={() => handleClose('/projects/add')}>
<MenuItem data-testid="add-menu-option" onClick={() => handleClose('/add/project')}>
<ListItemIcon>
<ListAltIcon/>
</ListItemIcon>
Expand All @@ -91,7 +91,7 @@ export default function AddMenu() {
{
// ADMIN ONLY options
user?.role==='rsd_admin' ?
<MenuItem data-testid="add-menu-option" onClick={() => handleClose('/news/add')}>
<MenuItem data-testid="add-menu-option" onClick={() => handleClose('/add/news')}>
<ListItemIcon>
<NewspaperIcon/>
</ListItemIcon>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@ import {Provider} from 'pages/api/fe/auth'
function set_location_cookie() {
// set cookie so that user is bounced to the software submission page
// after authentication
document.cookie = 'rsd_pathname=/software/add;path=/auth;SameSite=None;Secure'
document.cookie = 'rsd_pathname=/add/software;path=/auth;SameSite=None;Secure'
}

function submit_software_href(auth_status: string, login_providers: Provider[]) {
if (auth_status == 'authenticated') {
return '/software/add'
return '/add/software'
}
return (login_providers[0]?.redirectUrl ?? '')
}
Expand Down
68 changes: 34 additions & 34 deletions frontend/components/news/add/AddNewsCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@ import CircularProgress from '@mui/material/CircularProgress'
import Button from '@mui/material/Button'
import {useForm} from 'react-hook-form'

import {useAuth} from '~/auth'
import {useSession} from '~/auth'
import {useDebounce} from '~/utils/useDebounce'
import {getSlugFromString} from '~/utils/getSlugFromString'
import TextFieldWithCounter from '~/components/form/TextFieldWithCounter'
import SlugTextField from '~/components/form/SlugTextField'
import SubmitButtonWithListener from '~/components/form/SubmitButtonWithListener'
import ControlledTextField from '~/components/form/ControlledTextField'
import {AddNewsItem, addNewsItem, validSlugNews} from '~/components/news/apiNews'
import {newsConfig as config} from './newsConfig'
import ControlledTextField from '~/components/form/ControlledTextField'

type AddNewsForm = {
title: string|null,
Expand All @@ -35,13 +35,12 @@ const initialState = {
}

export default function AddNewsCard() {
const {session} = useAuth()
const {token} = useSession()
const router = useRouter()
const [baseUrl, setBaseUrl] = useState('')
const [slugValue, setSlugValue] = useState('')
const [validating, setValidating]=useState(false)
const [state, setState] = useState(initialState)
const {register, control, handleSubmit, watch, formState, setError, setValue, clearErrors} = useForm<AddNewsForm>({
const {register, control, handleSubmit, watch, formState, setError, setValue} = useForm<AddNewsForm>({
mode: 'onChange',
defaultValues:{
title: null,
Expand All @@ -54,14 +53,13 @@ export default function AddNewsCard() {
// watch for data change in the form
const [slug,title,publication_date] = watch(['slug','title','publication_date'])
// construct slug from title
const bouncedSlug = useDebounce(slugValue,700)
const bouncedSlug = useDebounce(slug ?? '',700)

// console.group('AddNewsCard')
// console.log('publication_date...', publication_date)
// console.log('isValid...', isValid)
// console.groupEnd()


useEffect(() => {
if (typeof location != 'undefined') {
setBaseUrl(`${location.origin}/news/${publication_date}/`)
Expand All @@ -75,54 +73,57 @@ export default function AddNewsCard() {
*/
useEffect(() => {
if (title){
const softwareSlug = getSlugFromString(title)
setSlugValue(softwareSlug)
const slugValue = getSlugFromString(title)
// update slugValue
setValue('slug',slugValue,{shouldValidate:true,shouldDirty:true})
}
}, [title])
}, [title, setValue])

/**
* When bouncedSlug value is changed,
* we need to update slug value (value in the input) shown to user.
* This slug changes when the title value is changed
* When bouncedSlug value is changed by debounce we check if slug is already
* used by existing news entries.
*/
useEffect(() => {
if (bouncedSlug){
setValue('slug', bouncedSlug, {
shouldValidate: true
})
}
}, [bouncedSlug, setValue])

useEffect(() => {
let abort = false

async function validateSlug() {
if (slug===null || publication_date===null) return
setValidating(true)
const isUsed = await validSlugNews({
date: publication_date,
slug,
token: session?.token
date: publication_date ?? '',
slug: bouncedSlug,
token
})
if (abort) return
if (isUsed === true) {
const message = `${publication_date}/${slug} is already taken. Use letters, numbers and dash "-" to modify slug value or change publication date.`
const message = `${publication_date}/${bouncedSlug} is already taken. Use letters, numbers and dash "-" to modify slug value or change publication date.`
setError('slug', {
type: 'validate',
message
})
}
lastValidatedSlug = `${publication_date}/${slug}`
lastValidatedSlug = `${publication_date}/${bouncedSlug}`
// we need to wait some time
setValidating(false)
}

if (`${publication_date}/${slug}` !== lastValidatedSlug) {
clearErrors()
if (bouncedSlug && publication_date &&
`${publication_date}/${bouncedSlug}` !== lastValidatedSlug
) {
// clearErrors()
// debugger
validateSlug()
}
return ()=>{abort=true}
},[slug,publication_date,session?.token,setError,clearErrors])
},[bouncedSlug,publication_date,token,setError])

useEffect(()=>{
// As soon as the slug value start changing we signal to user that we need to validate new slug.
// New slug value is "debounced" into variable bouncedSlug after the user stops typing.
// Another useEffect monitors bouncedSlug value and performs the validation.
// Validating flag disables Save button from the moment the slug value is changed until the validation is completed (see line 115).
if (slug && slug !== lastValidatedSlug){
// debugger
setValidating(true)
}
},[slug])

function handleCancel() {
// on cancel we send user back to previous page
Expand All @@ -131,7 +132,6 @@ export default function AddNewsCard() {

function onSubmit(data: AddNewsForm) {
// console.log('onSubmit...', data)
const {token} = session
// set flags
if (token && data) {
setState({
Expand All @@ -154,7 +154,7 @@ export default function AddNewsCard() {
}).then(resp => {
if (resp.status === 200) {
// redirect to edit page
// and remove software/add route from the history
// and remove /add/news route from the history
router.replace(`/news/${page.publication_date}/${page.slug}/edit`)
} else {
// show error
Expand Down
2 changes: 1 addition & 1 deletion frontend/components/projects/add/AddProjectCard.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@

import {render,screen,fireEvent,waitFor,waitForElementToBeRemoved,act} from '@testing-library/react'
import {WithAppContext, mockSession} from '~/utils/jest/WithAppContext'
import {getSlugFromString} from '~/utils/getSlugFromString'

import AddProjectCard from './AddProjectCard'
import {addConfig} from './addProjectConfig'
import {getSlugFromString} from '../../../utils/getSlugFromString'

const mockAddProject = jest.fn((props)=>Promise.resolve({status: 201, message: props}))
// eslint-disable-next-line @typescript-eslint/no-unused-vars
Expand Down
64 changes: 35 additions & 29 deletions frontend/components/projects/add/AddProjectCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ export default function AddProjectCard() {
const {token} = useSession()
const router = useRouter()
const [baseUrl, setBaseUrl] = useState('')
const [slugValue, setSlugValue] = useState('')
const [validating, setValidating]=useState(false)
const [state, setState] = useState(initialState)
const {register, handleSubmit, watch, formState, setError, setValue} = useForm<AddProjectForm>({
Expand All @@ -55,7 +54,15 @@ export default function AddProjectCard() {
// watch for data change in the form
const [slug,project_title,project_subtitle] = watch(['slug', 'project_title', 'project_subtitle'])
// construct slug from title
const bouncedSlug = useDebounce(slugValue,700)
const bouncedSlug = useDebounce(slug,700)

// console.group('AddProjectCard')
// console.log('slug...', slug)
// console.log('lastValidatedSlug...', lastValidatedSlug)
// console.log('bouncedSlug...', bouncedSlug)
// console.log('errors...', errors)
// console.log('validating...', validating)
// console.groupEnd()

useEffect(() => {
if (typeof location != 'undefined') {
Expand All @@ -73,47 +80,46 @@ export default function AddProjectCard() {
if (project_title) {
const slugValue = getSlugFromString(project_title)
// update slugValue
setSlugValue(slugValue)
}
}, [project_title])
/**
* When bouncedSlug value is changed,
* we need to update slug value (value in the input) shown to user.
* This change occures when brand_name value is changed
*/
useEffect(() => {
if (bouncedSlug) {
setValue('slug', bouncedSlug, {
shouldValidate: true
})
setValue('slug',slugValue,{shouldValidate:true,shouldDirty:true})
}
}, [bouncedSlug, setValue])
}, [project_title, setValue])

/**
* When slug value is changed by debounce or manually by user
* In addition to basic validations we also check if slug is already
* used by existing software entries. I moved this validation here
* because react-hook-form async validate function calls api 2 times.
* Further investigation about this is needed. For now we move it here.
* When bouncedSlug value is changed by debounce we check if slug is already
* used by existing project entries.
*/
useEffect(() => {
let abort = false
async function validateSlug() {
setValidating(true)
const isUsed = await validProjectItem(slug, token)
const isUsed = await validProjectItem(bouncedSlug, token)
if (abort) return
if (isUsed === true) {
const message = `${slug} is already taken. Use letters, numbers and dash "-" to modify slug value.`
const message = `${bouncedSlug} is already taken. Use letters, numbers and dash "-" to modify slug value.`
setError('slug',{type:'validate',message})
}
lastValidatedSlug = slug

lastValidatedSlug = bouncedSlug
setValidating(false)
}
if (slug && token && slug !== lastValidatedSlug) {
if (bouncedSlug && token && bouncedSlug !== lastValidatedSlug) {
validateSlug()
}
},[slug,token,setError])
return ()=>{abort=true}
},[bouncedSlug,token,setError])

useEffect(()=>{
// As soon as the slug value start changing we signal to user that we need to validate new slug.
// New slug value is "debounced" into variable bouncedSlug after the user stops typing.
// Another useEffect monitors bouncedSlug value and performs the validation.
// Validating flag disables Save button from the moment the slug value is changed until the validation is completed (see line 115).
if (slug && slug !== lastValidatedSlug){
// debugger
setValidating(true)
}
},[slug])

function handleCancel() {
// on cancel we send user back to prevous page
// on cancel we send user back to previous page
router.back()
}

Expand Down Expand Up @@ -149,7 +155,7 @@ export default function AddProjectCard() {
}).then(resp => {
if (resp.status === 201) {
// redirect to edit page
// and remove projects/add route from the history
// and remove /add/project route from the history
router.replace(`/projects/${project.slug}/edit`)
} else {
// show error
Expand Down
Loading

0 comments on commit 2da89e3

Please sign in to comment.