Skip to content

Commit

Permalink
Merge branch 'main' into manage-users
Browse files Browse the repository at this point in the history
  • Loading branch information
nathanielrindlaub authored Oct 24, 2023
2 parents 22fb5cc + 42d6cec commit 7c9f202
Show file tree
Hide file tree
Showing 9 changed files with 376 additions and 4 deletions.
13 changes: 13 additions & 0 deletions src/api/buildQuery.js
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,19 @@ const queries = {
variables: { input: input }
}),

createProject: (input) => ({
template: `
mutation CreateProject($input: CreateProjectInput!) {
createProject(input: $input) {
project {
${projectFields}
}
}
}
`,
variables: { input: input }
}),

getViews: (input) => ({
template: `
{
Expand Down
2 changes: 2 additions & 0 deletions src/app/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import NavBar from '../components/NavBar';
import HomePage from '../pages/HomePage';
import CaseStudiesPage from '../pages/CaseStudiesPage';
import AppPage from '../pages/AppPage';
import CreateProjectPage from '../pages/CreateProjectPage';
import { Amplify } from 'aws-amplify';
import { useAuthenticator } from '@aws-amplify/ui-react';
import * as Tooltip from '@radix-ui/react-tooltip';
Expand Down Expand Up @@ -121,6 +122,7 @@ const App = () => {
<Route exact path="/" component={HomePage} />
<Route path="/app" component={AppPage} />
<Route path="/case-studies" component={CaseStudiesPage} />
<Route path="/create-project" component={CreateProjectPage} />
{/*<Route component={NoMatch} />*/}
</Switch>
</AppContainer>
Expand Down
5 changes: 5 additions & 0 deletions src/components/ErrorAlerts.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import {
dismissDeploymentsError,
selectModelsErrors,
dismissModelsError,
selectCreateProjectsErrors,
dismissCreateProjectError,
} from '../features/projects/projectsSlice';
import {
selectWirelessCamerasErrors,
Expand Down Expand Up @@ -55,6 +57,7 @@ const ErrorAlerts = () => {
const statsErrors = useSelector(selectStatsErrors);
const exportErrors = useSelector(selectExportErrors);
const manageUserErrors = useSelector(selectManageUserErrors);
const createProjectErrors = useSelector(selectCreateProjectsErrors);

const enrichedErrors = [
enrichErrors(labelsErrors, 'Label Error', 'labels'),
Expand All @@ -68,6 +71,7 @@ const ErrorAlerts = () => {
enrichErrors(statsErrors, 'Error Getting Stats', 'stats'),
enrichErrors(exportErrors, 'Error Exporting Data', 'data'),
enrichErrors(manageUserErrors, 'Manage user error', 'manageUsers'),
enrichErrors(createProjectErrors, 'Error Creating Project', 'createProject'),
];

const errors = enrichedErrors.reduce((acc, curr) => (
Expand Down Expand Up @@ -116,6 +120,7 @@ const ErrorAlerts = () => {
const dismissErrorActions = {
'labels': (i) => dismissLabelsError(i),
'projects': (i) => dismissProjectsError(i),
'createProject': (i) => dismissCreateProjectError(i),
'views': (i) => dismissViewsError(i),
'deployments': (i) => dismissDeploymentsError(i),
'models': (i) => dismissModelsError(i),
Expand Down
20 changes: 20 additions & 0 deletions src/components/NotFound.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { styled } from '../theme/stitches.config';
import { Box } from './Box';

const Header = styled('div', {
fontSize: '42px',
fontWeight: '$5',
fontFamily: '$roboto',
color: '$textDark',
textAlign: 'center',
paddingTop: '$8',
paddingBottom: '$8'
});

export const NotFound = () => {
return (
<Box>
<Header>Page not found</Header>
</Box>
)
}
2 changes: 2 additions & 0 deletions src/components/SelectField.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ const SelectField = ({
error,
touched,
isSearchable,
isMulti
}) => {

const handleChange = (value) => {
Expand All @@ -85,6 +86,7 @@ const SelectField = ({
className='react-select'
classNamePrefix='react-select'
isSearchable={isSearchable}
isMulti={isMulti}
/>
{!!error &&
touched && (
Expand Down
182 changes: 182 additions & 0 deletions src/features/projects/CreateProjectForm.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
import { useEffect, useMemo } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { Formik, Form, Field } from 'formik';
import * as Yup from 'yup';
import { timeZonesNames } from '@vvo/tzdb';
import _ from 'lodash';
import { Cross2Icon } from '@radix-ui/react-icons';

import { styled } from '../../theme/stitches.config.js';
import { FormWrapper, FormFieldWrapper, FieldRow, ButtonRow, FormError } from '../../components/Form';
import Button from '../../components/Button.jsx';
import SelectField from '../../components/SelectField.jsx';
import {
Toast,
ToastTitle,
ToastAction,
ToastViewport
} from '../../components/Toast';
import IconButton from '../../components/IconButton';
import ErrorAlerts from '../../components/ErrorAlerts.jsx';
import { SimpleSpinner, SpinnerOverlay } from '../../components/Spinner.jsx';

import {
createProject,
selectCreateProjectState,
dismissStateMsg,
fetchModelOptions,
selectModelOptions,
selectCreateProjectLoading,
selectModelOptionsLoading
} from './projectsSlice.js';

const PageWrapper = styled('div', {
maxWidth: '600px',
padding: '0 $5',
width: '100%',
margin: '0 auto'
});

const Header = styled('div', {
fontSize: '24px',
fontWeight: '$5',
fontFamily: '$roboto',
paddingTop: '$8',
marginBottom: '$4'
});

const createProjectSchema = Yup.object().shape({
name: Yup.string().required('Enter a project name'),
description: Yup.string().required('Enter a short description'),
timezone: Yup.string().required('Select a timezone'),
availableMLModels: Yup.array().min(1, "Select at least one ML model").required('Select a ML model'),
});

const CreateProjectForm = () => {
const dispatch = useDispatch();
const stateMsg = useSelector(selectCreateProjectState);
const mlModels = useSelector(selectModelOptions);
const createProjectIsLoading = useSelector(selectCreateProjectLoading);
const mlModelsOptionsIsLoading = useSelector(selectModelOptionsLoading)

const tzOptions = timeZonesNames.map((tz) => ({ value: tz, label: tz }));
const mlModelOptions = useMemo(
() => mlModels.map(({ _id, description }) => ({
value: _id,
label: description
})),
[mlModels]
);

useEffect(() => {
dispatch(fetchModelOptions());
}, []);

return (
<PageWrapper>
{(createProjectIsLoading || mlModelsOptionsIsLoading) &&
<SpinnerOverlay>
<SimpleSpinner />
</SpinnerOverlay>
}
<Header>Create project</Header>
<FormWrapper>
<Formik
initialValues={{
name: '',
description: '',
timezone: '',
availableMLModels: []
}}
validationSchema={createProjectSchema}
onSubmit={(values) => dispatch(createProject(values))}
>
{({ values, errors, isValid, touched, setFieldTouched, setFieldValue }) => (
<Form>
<FieldRow>
<FormFieldWrapper>
<label htmlFor='name'>Name</label>
<Field name='name' id='name'/>
{!!errors.name && touched.name && (
<FormError>
{errors.name}
</FormError>
)}
</FormFieldWrapper>
</FieldRow>
<FieldRow>
<FormFieldWrapper>
<label htmlFor='description'>Description</label>
<Field as="textarea" name='description' id='description'/>
{!!errors.description && touched.description && (
<FormError>
{errors.description}
</FormError>
)}
</FormFieldWrapper>
</FieldRow>
<FieldRow>
<FormFieldWrapper>
<SelectField
name='timezone'
label='Timezone'
options={tzOptions}
value={tzOptions.find(({ value }) => value === values.timezone)}
touched={touched.timezone}
onChange={(name, { value }) => setFieldValue(name, value)}
onBlur={(name, { value }) => setFieldTouched(name, value)}
error={errors.timezone}
/>
</FormFieldWrapper>
</FieldRow>
<FieldRow>
<FormFieldWrapper>
<SelectField
name='availableMLModels'
label='Available ML models'
options={mlModelOptions}
value={mlModelOptions.filter(({ value }) => values.availableMLModels.includes(value))}
touched={touched.availableMLModels}
onChange={(name, value) => {
setFieldValue(name, value.map((model) => model.value))
}}
onBlur={(name, { value }) => setFieldTouched(name, value)}
error={errors.availableMLModels}
isMulti
/>
</FormFieldWrapper>
</FieldRow>
<ButtonRow>
<Button type='submit' size='large' disabled={!isValid}>
Save
</Button>
</ButtonRow>
{stateMsg && (
<>
<Toast
open={!!stateMsg}
duration={2000}
onOpenChange={() => dispatch(dismissStateMsg())}
>
<ToastTitle variant="green" css={{ marginBottom: 0 }}>
{stateMsg}
</ToastTitle>
<ToastAction asChild altText="Dismiss">
<IconButton variant='ghost'>
<Cross2Icon/>
</IconButton>
</ToastAction>
</Toast>
<ToastViewport />
</>
)}
</Form>
)}
</Formik>
</FormWrapper>
<ErrorAlerts />
</PageWrapper>
);
}

export default CreateProjectForm;
Loading

0 comments on commit 7c9f202

Please sign in to comment.