diff --git a/client/app/components/packages/projects/new.hbs b/client/app/components/packages/projects/new.hbs new file mode 100644 index 00000000..589721b5 --- /dev/null +++ b/client/app/components/packages/projects/new.hbs @@ -0,0 +1,45 @@ + +
+
+
+

+ Project Initiation Form +

+

+ The Project Initiation Form begins the project tracking process and gives NYC Planning the necessary + details before the Informational Interest meeting. By submitting this form, you confirm your intent to file a + Land Use Application with NYC Planning. +

+

+ While some projects might still be in the early stages, all required fields (*) on this form must be completed. + Once submitted, your project will be available to view in this portal. After reviewing your submission, NYC + Planning will contact you with the next steps. +

+
+ +
+ + +
+
\ No newline at end of file diff --git a/client/app/components/packages/projects/new.js b/client/app/components/packages/projects/new.js new file mode 100644 index 00000000..0c688125 --- /dev/null +++ b/client/app/components/packages/projects/new.js @@ -0,0 +1,26 @@ +import Component from '@glimmer/component'; +import { action } from '@ember/object'; +import { inject as service } from '@ember/service'; +import SubmittableProjectsNewForm from '../../../validations/submittable-projects-new-form'; + + +export default class ProjectsNewFormComponent extends Component { + validations = { + SubmittableProjectsNewForm, + }; + + @service + router; + + @service + store; + + @action + async submitPackage() { + try { + await this.args.package.submit(); + } catch (error) { + console.log('Save new project package error:', error); + } + } +} diff --git a/client/app/components/packages/projects/projects-new-information.hbs b/client/app/components/packages/projects/projects-new-information.hbs new file mode 100644 index 00000000..7cd975ef --- /dev/null +++ b/client/app/components/packages/projects/projects-new-information.hbs @@ -0,0 +1,18 @@ +{{#let @form as |form|}} + + + + + Project Name + + + + + +{{/let}} \ No newline at end of file diff --git a/client/app/models/package.js b/client/app/models/package.js index 0493d765..729341a9 100644 --- a/client/app/models/package.js +++ b/client/app/models/package.js @@ -85,10 +85,10 @@ export default class PackageModel extends Model { dcpVisibility; @attr('number') - dcpPackageversion + dcpPackageversion; @attr('string') - dcpPackagenotes + dcpPackagenotes; @attr({ defaultValue: () => [] }) documents; @@ -101,9 +101,11 @@ export default class PackageModel extends Model { } get isLUPackage() { - return this.dcpPackagetype === DCPPACKAGETYPE.DRAFT_LU_PACKAGE.code - || this.dcpPackagetype === DCPPACKAGETYPE.FILED_LU_PACKAGE.code - || this.dcpPackagetype === DCPPACKAGETYPE.POST_CERT_LU.code; + return ( + this.dcpPackagetype === DCPPACKAGETYPE.DRAFT_LU_PACKAGE.code + || this.dcpPackagetype === DCPPACKAGETYPE.FILED_LU_PACKAGE.code + || this.dcpPackagetype === DCPPACKAGETYPE.POST_CERT_LU.code + ); } setAttrsForSubmission() { @@ -192,15 +194,18 @@ export default class PackageModel extends Model { try { await this.fileManager.save(); } catch (e) { - console.log('Error saving files: ', e);// eslint-disable-line no-console + console.log('Error saving files: ', e); // eslint-disable-line no-console // See comment on the tracked fileUploadError property // definition above. - this.fileUploadErrors = [{ - code: 'UPLOAD_DOC_FAILED', - title: 'Failed to upload documents', - detail: 'An error occured while uploading your documents. Please refresh and retry.', - }]; + this.fileUploadErrors = [ + { + code: 'UPLOAD_DOC_FAILED', + title: 'Failed to upload documents', + detail: + 'An error occured while uploading your documents. Please refresh and retry.', + }, + ]; } if (this.isLUPackage && this.project.artifact) { @@ -211,11 +216,14 @@ export default class PackageModel extends Model { // See comment on the tracked fileUploadError property // definition above. - this.fileUploadErrors = [{ - code: 'UPLOAD_DOC_FAILED', - title: 'Failed to upload artifact documents', - detail: 'An error occured while uploading your documents. Please refresh and retry.', - }]; + this.fileUploadErrors = [ + { + code: 'UPLOAD_DOC_FAILED', + title: 'Failed to upload artifact documents', + detail: + 'An error occured while uploading your documents. Please refresh and retry.', + }, + ]; } } @@ -233,7 +241,8 @@ export default class PackageModel extends Model { get isSingleCeqrInvoiceQuestionnaireDirty() { if (this.singleCeqrInvoiceQuestionnaire) { return this.singleCeqrInvoiceQuestionnaire.hasDirtyAttributes; - } return false; + } + return false; } async saveDirtySingleCeqrInvoiceQuestionnaire() { @@ -250,10 +259,7 @@ export default class PackageModel extends Model { async saveDeletedRecords(recordsToDelete) { if (recordsToDelete) { - return Promise.all( - recordsToDelete - .map((record) => record.save()), - ); + return Promise.all(recordsToDelete.map((record) => record.save())); } } @@ -267,22 +273,28 @@ export default class PackageModel extends Model { get isDirty() { const isPackageDirty = this.hasDirtyAttributes - || this.fileManager.isDirty || (this.isLUPackage && this.project.isDirty); + || this.fileManager.isDirty + || (this.isLUPackage && this.project.isDirty); if (this.dcpPackagetype === DCPPACKAGETYPE.PAS_PACKAGE.code) { - return isPackageDirty + return ( + isPackageDirty || this.pasForm.hasDirtyAttributes || this.pasForm.isBblsDirty || this.pasForm.isApplicantsDirty - || this.pasForm.isProjectDirty; + || this.pasForm.isProjectDirty + ); } if (this.dcpPackagetype === DCPPACKAGETYPE.RWCDS.code) { - return isPackageDirty + return ( + isPackageDirty || this.rwcdsForm.hasDirtyAttributes - || this.rwcdsForm.isAffectedZoningResolutionsDirty; + || this.rwcdsForm.isAffectedZoningResolutionsDirty + ); } if (this.isLUPackage) { - return isPackageDirty + return ( + isPackageDirty || this.landuseForm.hasDirtyAttributes || this.landuseForm.isBblsDirty || this.landuseForm.isApplicantsDirty @@ -291,15 +303,14 @@ export default class PackageModel extends Model { || this.landuseForm.isLanduseGeographiesDirty || this.landuseForm.isRelatedActionsDirty || this.landuseForm.isAffectedZoningResolutionsDirty - || this.landuseForm.isZoningMapChangesDirty; + || this.landuseForm.isZoningMapChangesDirty + ); } if (this.dcpPackagetype === DCPPACKAGETYPE.FILED_EAS.code) { - return isPackageDirty - || this.isSingleCeqrInvoiceQuestionnaireDirty; + return isPackageDirty || this.isSingleCeqrInvoiceQuestionnaireDirty; } if (this.dcpPackagetype === DCPPACKAGETYPE.DRAFT_SCOPE_OF_WORK.code) { - return isPackageDirty - || this.isSingleCeqrInvoiceQuestionnaireDirty; + return isPackageDirty || this.isSingleCeqrInvoiceQuestionnaireDirty; } return isPackageDirty; diff --git a/client/app/models/project.js b/client/app/models/project.js index dcf7ae07..d3302998 100644 --- a/client/app/models/project.js +++ b/client/app/models/project.js @@ -233,4 +233,8 @@ export default class ProjectModel extends Model { .sortBy('dcpPackageversion') .reverse(); } + + async submit() { + await super.save(); + } } diff --git a/client/app/router.js b/client/app/router.js index 9d563e78..18bad84f 100644 --- a/client/app/router.js +++ b/client/app/router.js @@ -11,6 +11,9 @@ export default class Router extends EmberRouterScroll { Router.map(function() { // eslint-disable-line this.route('projects'); + + this.route(config.featureFlagSelfService ? 'projects/new' : 'not-found', { path: 'projects/new' }); + this.route('login'); this.route('logout'); diff --git a/client/app/routes/projects/new.js b/client/app/routes/projects/new.js new file mode 100644 index 00000000..4483eec6 --- /dev/null +++ b/client/app/routes/projects/new.js @@ -0,0 +1,11 @@ +import Route from '@ember/routing/route'; +import { inject as service } from '@ember/service'; +import AuthenticatedRouteMixin from 'ember-simple-auth/mixins/authenticated-route-mixin'; + +export default class ProjectsNewRoute extends Route.extend(AuthenticatedRouteMixin) { + @service store; + + async model() { + return await this.store.createRecord('project'); + } +} diff --git a/client/app/templates/projects/new.hbs b/client/app/templates/projects/new.hbs new file mode 100644 index 00000000..36675f77 --- /dev/null +++ b/client/app/templates/projects/new.hbs @@ -0,0 +1,10 @@ + + + + + + + +{{outlet}} \ No newline at end of file diff --git a/client/app/validations/submittable-projects-new-form.js b/client/app/validations/submittable-projects-new-form.js new file mode 100644 index 00000000..2c00a391 --- /dev/null +++ b/client/app/validations/submittable-projects-new-form.js @@ -0,0 +1,18 @@ +import { + validateLength, + validatePresence, +} from 'ember-changeset-validations/validators'; + +export default { + dcpProjectname: [ + validateLength({ + min: 0, + max: 50, + message: 'Text is too long (max {max} characters)', + }), + validatePresence({ + presence: true, + message: 'This field is required', + }), + ], +}; diff --git a/client/tests/unit/routes/projects/new-test.js b/client/tests/unit/routes/projects/new-test.js new file mode 100644 index 00000000..c6c8f1e0 --- /dev/null +++ b/client/tests/unit/routes/projects/new-test.js @@ -0,0 +1,11 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Route | projects/new', function(hooks) { + setupTest(hooks); + + test('it exists', function(assert) { + const route = this.owner.lookup('route:projects/new'); + assert.ok(route); + }); +}); diff --git a/server/src/projects/projects.controller.ts b/server/src/projects/projects.controller.ts index bf12a75b..c5f7dc83 100644 --- a/server/src/projects/projects.controller.ts +++ b/server/src/projects/projects.controller.ts @@ -3,7 +3,6 @@ import { Get, Patch, Body, - Query, HttpException, HttpStatus, Session, @@ -11,6 +10,7 @@ import { UseGuards, UsePipes, Param, + Post, } from '@nestjs/common'; import { ProjectsService } from './projects.service'; import { ConfigService } from '../config/config.service'; @@ -129,6 +129,21 @@ export class ProjectsController { } } + @Post('/') + async createProject(@Body() body) { + if (!this.config.featureFlag.selfService) { + throw new HttpException({ + code: "NOT_FOUND", + title: "Not found", + }, + HttpStatus.NOT_FOUND) + } + + return { + ...body + } + } + @Get('/:id') async projectById(@Param('id') id) { try {