Skip to content

Commit

Permalink
Merge pull request #466 from kitspace/conflict-modal
Browse files Browse the repository at this point in the history
Add  `ConflictModal` and `SyncConflictModal` components
  • Loading branch information
kasbah authored Nov 16, 2022
2 parents c1bf0e0 + c6db11d commit 79745aa
Show file tree
Hide file tree
Showing 18 changed files with 816 additions and 215 deletions.
6 changes: 3 additions & 3 deletions e2e/cypress/integration/uploadProject.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ describe('Upload project', () => {
})

cy.get('[data-cy=creating-project-loader]')
cy.url().should('eq', `${Cypress.config().baseUrl}/${username}/example`)
cy.url().should('eq', `${Cypress.config().baseUrl}/${username}/example.png`)

// TODO FIXME, when writing test for project page
// cy.get('[data-cy=file-name]', { timeout: 15000 }).contains('example.png')
Expand Down Expand Up @@ -61,7 +61,7 @@ describe('User projects name collision', () => {
})

cy.get('[data-cy=creating-project-loader]')
cy.url().should('include', `${username}/example`)
cy.url().should('include', `${username}/example.png`)
})

beforeEach(() => {
Expand Down Expand Up @@ -103,7 +103,7 @@ describe('User projects name collision', () => {
cy.get('[data-cy=collision-update]').click()

// redirect to the upload page
cy.url().should('eq', `${Cypress.config().baseUrl}/${username}/example`)
cy.url().should('eq', `${Cypress.config().baseUrl}/${username}/example.png`)

// TODO FIXME, when writing test for project page
// The new file is committed and on the update page
Expand Down
17 changes: 14 additions & 3 deletions frontend/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
{
"$schema": "https://raw.githubusercontent.com/SchemaStore/schemastore/master/src/schemas/json/eslintrc.json",
"extends": ["next", "next/core-web-vitals", "prettier"],
"extends": [
"next",
"next/core-web-vitals",
"prettier",
"plugin:@typescript-eslint/recommended"
],
"settings": {
"import/resolver": {
"alias": {
Expand All @@ -19,17 +24,23 @@
"browser": true,
"es2021": true
},
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaFeatures": {
"jsx": true
},
"ecmaVersion": 12,
"sourceType": "module"
},
"plugins": ["prettier"],
"plugins": ["prettier", "@typescript-eslint"],
"rules": {
"no-console": ["error", { "allow": ["warn", "error"] }],
"no-unused-vars": ["error", { "varsIgnorePattern": "[iI]gnored" }],
"@typescript-eslint/no-unused-vars": [
"error",
{ "varsIgnorePattern": "[iI]gnored" }
],
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-empty-function": "off",
"no-param-reassign": "off",
"no-prototype-builtins": "off",
"no-underscore-dangle": "off",
Expand Down
6 changes: 4 additions & 2 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@
"react-social-login-buttons": "3.1.2",
"semantic-ui-css": "^2.4.1",
"semantic-ui-react": "^2.0.3",
"slugify": "^1.4.6",
"styled-jsx": "^5.0.0",
"swr": "^1.3.0",
"url-loader": "^4.1.0",
Expand All @@ -45,6 +44,8 @@
"@testing-library/react": "12.1.2",
"@types/lodash": "^4.14.188",
"@types/react": "^17.0.0",
"@typescript-eslint/eslint-plugin": "^5.41.0",
"@typescript-eslint/parser": "^5.41.0",
"@vitejs/plugin-react": "^2.1.0",
"eslint": ">=7.0.0",
"eslint-config-next": "^11.0.0",
Expand All @@ -53,9 +54,10 @@
"eslint-plugin-import": "^2.23.4",
"eslint-plugin-prettier": "3.4.0",
"jsdom": "^20.0.1",
"next-router-mock": "^0.7.4",
"prettier": "^2.0.5",
"sass": "^1.49.6",
"typescript": "~4.3.5",
"typescript": "^4.8.4",
"vite-tsconfig-paths": "^3.5.1",
"vitest": "^0.24.2",
"vitest-fetch-mock": "^0.2.1"
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/Board/BuyParts/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import styles from './index.module.scss'
const BuyParts = ({ projectFullName, lines, parts }) => {
const [extensionPresence, setExtensionPresence] = useState('unknown')
// it's needed to fix the extension integration.
// eslint-disable-next-line no-unused-vars
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [buyParts, setBuyParts] = useState(null)
const [buyMultiplier, setBuyMultiplier] = useState(1)
const [mult, setMult] = useState(1)
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/DropZone.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import getConfig from 'next/config'

// `FileWithPath is used in the `fileCustomValidator` function typing.
// eslint-disable-next-line no-unused-vars
import { useDropzone, ErrorCode, FileWithPath } from 'react-dropzone'
import { useDropzone, ErrorCode } from 'react-dropzone'
import { fromEvent } from 'file-selector'
import { Button } from 'semantic-ui-react'

Expand Down
166 changes: 166 additions & 0 deletions frontend/src/components/NewProject/ConflictModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
import React, { useContext, useEffect } from 'react'
import { Button, Form, Input, Modal } from 'semantic-ui-react'

import useForm from '@hooks/useForm'
import ExistingProjectFromModel from '@models/ExistingProjectForm'
import { isUsableProjectName } from '@utils/giteaApi'
import { formatAsGiteaRepoName } from '@utils/index'
import { AuthContext } from '@contexts/AuthContext'

const ConflictModal = ({
conflictModalOpen,
onClose,
originalProjectName,
message,
onDifferentName,
onDifferentNameButtonContent,
onOverwrite,
onOverwriteButtonContent,
}: BaseConflictModalProps) => {
const { user } = useContext(AuthContext)
const { form, onChange, isValid, populate, formatErrorPrompt } = useForm(
ExistingProjectFromModel,
)

const [isValidProjectName, setIsValidProjectName] = React.useState(false)

const didChangeName = originalProjectName !== form.name
const projectName = formatAsGiteaRepoName(form.name || '')

useEffect(() => {
populate({ name: originalProjectName })
}, [originalProjectName, populate])

useEffect(() => {
if (form.name && didChangeName) {
isUsableProjectName(user.username, form.name, isValid).then(
setIsValidProjectName,
)
}
}, [user, form.name, isValid, didChangeName])

return (
<Modal
closeIcon
data-cy="collision-modal"
open={conflictModalOpen}
onClose={onClose}
>
<Modal.Header>A project with that name already exists.</Modal.Header>
<Modal.Content>
<p>{message}</p>
<Form>
<Form.Field
fluid
control={Input}
error={formatProjectNameError(
isValidProjectName,
didChangeName,
projectName,
formatErrorPrompt('name'),
)}
label={didChangeName ? 'New project name' : 'Project name'}
name="name"
value={form.name || ''}
onChange={onChange}
/>
</Form>
</Modal.Content>
<Modal.Actions>
{didChangeName ? (
<Button
color="green"
content={onDifferentNameButtonContent}
data-cy="collision-different-name"
disabled={!isValidProjectName}
onClick={() => onDifferentName(projectName)}
/>
) : (
<Button
color="orange"
content={onOverwriteButtonContent}
data-cy="collision-update"
onClick={onOverwrite}
/>
)}
</Modal.Actions>
</Modal>
)
}

export const SyncConflictModal = ({
conflictModalOpen,
onClose,
originalProjectName,
onDifferentName,
onOverwrite,
}: SyncConflictModalProps) => (
<ConflictModal
{...{
conflictModalOpen,
onClose,
originalProjectName,
onDifferentName,
onOverwrite,
}}
message="You have an imported a project with the same name. You can either overwrite the existing project or choose a different name."
onDifferentNameButtonContent="OK"
onOverwriteButtonContent="Overwrite"
/>
)

export const UploadConflictModal = ({
conflictModalOpen,
onClose,
onDifferentName,
onOverwrite,
originalProjectName,
}: SyncConflictModalProps) => (
<ConflictModal
{...{
conflictModalOpen,
onClose,
originalProjectName,
onDifferentName,
onOverwrite,
}}
message="You have an existing project with the same name. You can either choose a different name or update this file in the existing project."
onDifferentNameButtonContent="Choose a different name"
onOverwriteButtonContent="Update existing project"
/>
)

/**
* Disjoint form validation errors, e.g, maximum length, not empty, etc, with conflicting project name errors
* @returns
*/
const formatProjectNameError = (
isValidProjectName: boolean,
didChangeName: boolean,
projectName: string,
formErrors?: { content: any; pointing: string },
) => {
if (formErrors) {
return formErrors
}
return !isValidProjectName && didChangeName
? {
content: `A project named "${projectName}" already exists!`,
pointing: 'below',
}
: null
}

interface SyncConflictModalProps {
conflictModalOpen: boolean
onClose: () => void
onDifferentName: (name: string) => void
onOverwrite: () => void
originalProjectName: string
}

interface BaseConflictModalProps extends SyncConflictModalProps {
message: string
onDifferentNameButtonContent: string
onOverwriteButtonContent: string
}
Loading

0 comments on commit 79745aa

Please sign in to comment.