From e0ee8b5cad9d9627d7b586becc39b19a7fa97aec Mon Sep 17 00:00:00 2001
From: Ramon Wenger
Date: Tue, 31 Aug 2021 16:39:55 +0200
Subject: [PATCH 01/28] Add failing tests for new features
---
.../integration/frontend/new-project.spec.js | 57 -------
.../frontend/portfolio/new-project.spec.js | 56 +++++++
.../frontend/portfolio/project-entry.spec.js | 157 ++++++++++++++++++
.../frontend/portfolio/projects-page.spec.js | 50 ++++++
.../frontend/project-entry.spec.js | 151 -----------------
5 files changed, 263 insertions(+), 208 deletions(-)
delete mode 100644 client/cypress/integration/frontend/new-project.spec.js
create mode 100644 client/cypress/integration/frontend/portfolio/new-project.spec.js
create mode 100644 client/cypress/integration/frontend/portfolio/project-entry.spec.js
create mode 100644 client/cypress/integration/frontend/portfolio/projects-page.spec.js
delete mode 100644 client/cypress/integration/frontend/project-entry.spec.js
diff --git a/client/cypress/integration/frontend/new-project.spec.js b/client/cypress/integration/frontend/new-project.spec.js
deleted file mode 100644
index 905ee28a..00000000
--- a/client/cypress/integration/frontend/new-project.spec.js
+++ /dev/null
@@ -1,57 +0,0 @@
-const operations = {
- ProjectsQuery: {
- projects: {
- edges: [
- {
- node: {
- id: 'UHJvamVjdE5vZGU6NjY=',
- title: 'Some random title',
- appearance: 'blue',
- description: 'This description rocks',
- slug: 'some-random-title',
- objectives: 'Git gud',
- final: false,
- student: {
- firstName: 'Rachel',
- lastName: 'Green',
- id: 'VXNlck5vZGU6NQ==',
- avatarUrl: '',
- },
- entriesCount: 0,
- },
- },
- ],
- },
- },
- MeQuery: {
- me: {
- }
- },
- AddProject: variables => ({
- addProject: {
- project: Object.assign({}, variables.input.project),
- errors: null,
- __typename: 'AddProjectPayload',
- },
- }),
-};
-
-describe('New project', () => {
- before(() => {
- cy.setup();
- });
-
- it('creates a new project and displays it', () => {
- cy.mockGraphqlOps({
- operations
- });
- cy.visit('/portfolio');
-
- cy.get('[data-cy=add-project-button]').click();
- cy.get('[data-cy=page-form-input-titel]').type('Some random title');
- cy.get('[data-cy=page-form-input-beschreibung]').type('This description rocks');
- cy.get('[data-cy=page-form-input-ziele]').type('Git gud');
- cy.get('[data-cy=save-project-button]').click();
- cy.get('.project-widget:first').contains('random');
- });
-});
diff --git a/client/cypress/integration/frontend/portfolio/new-project.spec.js b/client/cypress/integration/frontend/portfolio/new-project.spec.js
new file mode 100644
index 00000000..86c43062
--- /dev/null
+++ b/client/cypress/integration/frontend/portfolio/new-project.spec.js
@@ -0,0 +1,56 @@
+describe('New project', () => {
+ const operations = {
+ ProjectsQuery: {
+ projects: {
+ edges: [
+ {
+ node: {
+ id: 'UHJvamVjdE5vZGU6NjY=',
+ title: 'Some random title',
+ appearance: 'blue',
+ description: 'This description rocks',
+ slug: 'some-random-title',
+ objectives: 'Git gud',
+ final: false,
+ student: {
+ firstName: 'Rachel',
+ lastName: 'Green',
+ id: 'VXNlck5vZGU6NQ==',
+ avatarUrl: '',
+ },
+ entriesCount: 0,
+ },
+ },
+ ],
+ },
+ },
+ MeQuery: {
+ me: {},
+ },
+ AddProject: variables => ({
+ addProject: {
+ project: Object.assign({}, variables.input.project),
+ errors: null,
+ __typename: 'AddProjectPayload',
+ },
+ }),
+ };
+
+ before(() => {
+ cy.setup();
+ });
+
+ it('creates a new project and displays it', () => {
+ cy.mockGraphqlOps({
+ operations,
+ });
+ cy.visit('/portfolio');
+
+ cy.get('[data-cy=add-project-button]').click();
+ cy.get('[data-cy=page-form-input-titel]').type('Some random title');
+ cy.get('[data-cy=page-form-input-beschreibung]').should('exist').should('be.empty');
+ cy.get('[data-cy=page-form-input-ziele]').should('not.exist');
+ cy.get('[data-cy=save-project-button]').click();
+ cy.get('.project-widget:first').contains('random');
+ });
+});
diff --git a/client/cypress/integration/frontend/portfolio/project-entry.spec.js b/client/cypress/integration/frontend/portfolio/project-entry.spec.js
new file mode 100644
index 00000000..a7ca7a85
--- /dev/null
+++ b/client/cypress/integration/frontend/portfolio/project-entry.spec.js
@@ -0,0 +1,157 @@
+describe('Project Entry', () => {
+ const operations = {
+ MeQuery: {
+ me: {
+ id: 'VXNlck5vZGU6NQ==',
+ permissions: [],
+ },
+ },
+ ProjectsQuery: {
+ projects: {
+ edges: [{
+ node: {
+ id: 'UHJvamVjdE5vZGU6MzM=',
+ title: 'Groot',
+ appearance: 'red',
+ 'description': 'I am Groot',
+ 'slug': 'groot',
+ 'objectives': 'Be Groot\nBe awesome',
+ 'final': false,
+ 'student': {
+ 'firstName': 'Rachel',
+ 'lastName': 'Green',
+ 'id': 'VXNlck5vZGU6NQ==',
+ 'avatarUrl': '',
+ '__typename': 'UserNode',
+ },
+ 'entriesCount': 2,
+ '__typename': 'ProjectNode',
+ },
+ '__typename': 'ProjectNodeEdge',
+ }],
+ '__typename': 'ProjectNodeConnection',
+ },
+ },
+ ProjectQuery: {
+ 'project': {
+ 'id': 'UHJvamVjdE5vZGU6MzY=',
+ 'title': 'Groot',
+ 'appearance': 'yellow',
+ 'description': 'I am Groot',
+ 'slug': 'groot',
+ 'objectives': 'Be Groot\nBe awesome',
+ 'final': false,
+ 'student': {
+ 'firstName': 'Rachel',
+ 'lastName': 'Green',
+ 'id': 'VXNlck5vZGU6NQ==',
+ 'avatarUrl': '',
+ '__typename': 'UserNode',
+ },
+ 'entriesCount': 1,
+ '__typename': 'ProjectNode',
+ 'entries': {
+ 'edges': [{
+ 'node': {
+ 'id': 'UHJvamVjdEVudHJ5Tm9kZTo2NQ==',
+ 'activity': 'Kill Thanos',
+ 'reflection': 'He sucks',
+ 'nextSteps': 'Go for the head',
+ 'documentUrl': '',
+ '__typename': 'ProjectEntryNode',
+ 'created': '2020-01-20T15:20:31.262510+00:00',
+ },
+ '__typename': 'ProjectEntryNodeEdge',
+ }],
+ '__typename': 'ProjectEntryNodeConnection',
+ },
+ },
+ },
+ AddProjectEntry: variables => ({
+ addProjectEntry: {
+ projectEntry: Object.assign({}, variables.input.projectEntry, {
+ created: '2020-01-20T15:26:58.722773+00:00',
+ }),
+ errors: null,
+ __typename: 'AddProjectEntryPayload',
+ },
+ }),
+ UpdateProjectEntry: variables => ({
+ updateProjectEntry: {
+ projectEntry: variables.input.projectEntry,
+ errors: null,
+ __typename: 'UpdateProjectEntryPayload',
+ },
+ }),
+ DeleteProjectEntry: {
+ deleteProjectEntry: {
+ success: true,
+ __typename: 'DeleteProjectEntryPayload',
+ },
+ },
+ };
+
+ beforeEach(() => {
+ cy.setup();
+
+ cy.task('getSchema').then(schema => {
+ cy.mockGraphqlOps({
+ operations,
+ });
+ });
+ });
+
+ it('should create a new project entry', () => {
+ cy.visit('/portfolio');
+ cy.get('[data-cy=project-link]:first-of-type').click();
+ cy.get('[data-cy=add-project-entry]:first-of-type').click();
+ cy.getByDataCy('activity-input').should('not.exist');
+ // cy.get('[data-cy=activity-input]').within(() => {
+ // cy.get('[data-cy=text-form-input]').type('Join the Guardians');
+ // });
+ cy.getByDataCy('reflection-input').should('not.exist');
+ // cy.get('[data-cy=reflection-input]').within(() => {
+ // cy.get('[data-cy=text-form-input]').type('They are cool!');
+ // });
+ cy.getByDataCy('next-steps-input').should('not.exist');
+ // cy.get('[data-cy=next-steps-input]').within(() => {
+ // cy.get('[data-cy=text-form-input]').type('Stay with Rocket\nMeet Quill');
+ // });
+ cy.getByDataCy('modal-title').should('contain', 'Beitrag erfassen');
+ cy.getByDataCy('project-entry-input').should('exist');
+ cy.getByDataCy('use-template-button').should('exist').click();
+ cy.getByDataCy('upload-document-button').should('exist');
+ cy.getByDataCy('modal-save-button').click();
+
+ cy.get('.project-entry:last-of-type').within(() => {
+ cy.get('.project-entry__paragraph:first-of-type').contains('Join the Guardians');
+ });
+ });
+
+ it('should edit first entry', () => {
+ cy.visit('/portfolio/groot');
+ cy.get('.project-entry__paragraph:first-of-type').contains('Kill Thanos');
+ cy.get('.project-entry:first-of-type').within(() => {
+ cy.get('[data-cy=project-entry-more]').click();
+ cy.get('[data-cy=edit-project-entry]').click();
+ });
+ cy.get('[data-cy=activity-input]').within(() => {
+ cy.get('[data-cy=text-form-input]').clear().type('Defeat Thanos');
+ });
+ cy.get('[data-cy=modal-save-button]').click();
+ cy.get('.project-entry__paragraph:first-of-type').contains('Defeat Thanos');
+ });
+
+ it('should delete the last entry', () => {
+ cy.visit('/portfolio/groot');
+
+ cy.get('.project-entry').should('have.length', 1);
+
+ cy.get('.project-entry:last-of-type').within(() => {
+ cy.get('[data-cy=project-entry-more]').click();
+ cy.get('[data-cy=delete-project-entry]').click();
+ });
+
+ cy.get('.project-entry').should('have.length', 0);
+ });
+});
diff --git a/client/cypress/integration/frontend/portfolio/projects-page.spec.js b/client/cypress/integration/frontend/portfolio/projects-page.spec.js
new file mode 100644
index 00000000..623f3d55
--- /dev/null
+++ b/client/cypress/integration/frontend/portfolio/projects-page.spec.js
@@ -0,0 +1,50 @@
+import {getMinimalMe} from '../../../support/helpers';
+
+describe('Projects page', () => {
+ const MeQuery = getMinimalMe({});
+ beforeEach(() => {
+ cy.setup();
+ });
+
+ it('displays portfolio onboarding', () => {
+ const operations = {
+ MeQuery,
+ ProjectsQuery: {
+ projects: {
+ edges: [],
+ },
+ },
+ };
+ cy.mockGraphqlOps({
+ operations,
+ });
+ cy.visit('/portfolio');
+ cy.getByDataCy('page-title').should('contain', 'Portfolio');
+ cy.getByDataCy('portfolio-onboarding-illustration').should('exist');
+ cy.getByDataCy('portfolio-onboarding-subtitle').should('contain', 'Woran denken Sie gerade');
+ cy.getByDataCy('portfolio-onboarding-text').should('contain', 'Hier können Sie Projekte erstellen.');
+ cy.getByDataCy('page-footer').should('not.exist');
+ cy.getByDataCy('create-project-button').should('exist');
+ });
+
+ it('displays the project list', () => {
+ const operations = {
+ MeQuery,
+ ProjectsQuery: {
+ projects: {
+ edges: [],
+ },
+ },
+ };
+
+ cy.mockGraphqlOps({
+ operations,
+ });
+ cy.visit('/portfolio');
+ cy.getByDataCy('page-title').should('contain', 'Portfolio');
+ cy.getByDataCy('create-project-button').should('exist');
+ cy.getByDataCy('project-list').should('exist');
+ cy.getByDataCy('project').should('have.length', 1);
+ cy.getByDataCy('project-owner').should('contain', 'Bilbo Baggins');
+ });
+});
diff --git a/client/cypress/integration/frontend/project-entry.spec.js b/client/cypress/integration/frontend/project-entry.spec.js
deleted file mode 100644
index 6fcfd2c7..00000000
--- a/client/cypress/integration/frontend/project-entry.spec.js
+++ /dev/null
@@ -1,151 +0,0 @@
-
-const operations = {
- MeQuery: {
- me: {
- id: 'VXNlck5vZGU6NQ==',
- permissions: [],
- },
- },
- ProjectsQuery: {
- projects: {
- edges: [{
- node: {
- id: 'UHJvamVjdE5vZGU6MzM=',
- title: 'Groot',
- appearance: 'red',
- 'description': 'I am Groot',
- 'slug': 'groot',
- 'objectives': 'Be Groot\nBe awesome',
- 'final': false,
- 'student': {
- 'firstName': 'Rachel',
- 'lastName': 'Green',
- 'id': 'VXNlck5vZGU6NQ==',
- 'avatarUrl': '',
- '__typename': 'UserNode',
- },
- 'entriesCount': 2,
- '__typename': 'ProjectNode',
- },
- '__typename': 'ProjectNodeEdge',
- }],
- '__typename': 'ProjectNodeConnection',
- },
- },
- ProjectQuery: {
- 'project': {
- 'id': 'UHJvamVjdE5vZGU6MzY=',
- 'title': 'Groot',
- 'appearance': 'yellow',
- 'description': 'I am Groot',
- 'slug': 'groot',
- 'objectives': 'Be Groot\nBe awesome',
- 'final': false,
- 'student': {
- 'firstName': 'Rachel',
- 'lastName': 'Green',
- 'id': 'VXNlck5vZGU6NQ==',
- 'avatarUrl': '',
- '__typename': 'UserNode',
- },
- 'entriesCount': 1,
- '__typename': 'ProjectNode',
- 'entries': {
- 'edges': [{
- 'node': {
- 'id': 'UHJvamVjdEVudHJ5Tm9kZTo2NQ==',
- 'activity': 'Kill Thanos',
- 'reflection': 'He sucks',
- 'nextSteps': 'Go for the head',
- 'documentUrl': '',
- '__typename': 'ProjectEntryNode',
- 'created': '2020-01-20T15:20:31.262510+00:00',
- },
- '__typename': 'ProjectEntryNodeEdge',
- }],
- '__typename': 'ProjectEntryNodeConnection',
- },
- },
- },
- AddProjectEntry: variables => ({
- addProjectEntry: {
- projectEntry: Object.assign({}, variables.input.projectEntry, {
- created: '2020-01-20T15:26:58.722773+00:00',
- }),
- errors: null,
- __typename: 'AddProjectEntryPayload',
- },
- }),
- UpdateProjectEntry: variables => ({
- updateProjectEntry: {
- projectEntry: variables.input.projectEntry,
- errors: null,
- __typename: 'UpdateProjectEntryPayload',
- },
- }),
- DeleteProjectEntry: {
- deleteProjectEntry: {
- success: true,
- __typename: 'DeleteProjectEntryPayload',
- },
- },
-};
-
-describe('Project Entry', () => {
- beforeEach(() => {
- cy.setup();
-
- cy.task('getSchema').then(schema => {
- cy.mockGraphqlOps({
- operations,
- });
- });
- });
-
- it('should create a new project entry', () => {
- cy.visit('/portfolio');
- cy.get('[data-cy=project-link]:first-of-type').click();
- cy.get('[data-cy=add-project-entry]:first-of-type').click();
- cy.get('[data-cy=activity-input]').within(() => {
- cy.get('[data-cy=text-form-input]').type('Join the Guardians');
- });
- cy.get('[data-cy=reflection-input]').within(() => {
- cy.get('[data-cy=text-form-input]').type('They are cool!');
- });
- cy.get('[data-cy=next-steps-input]').within(() => {
- cy.get('[data-cy=text-form-input]').type('Stay with Rocket\nMeet Quill');
- });
- cy.get('[data-cy=modal-save-button]').click();
-
- cy.get('.project-entry:last-of-type').within(() => {
- cy.get('.project-entry__paragraph:first-of-type').contains('Join the Guardians');
- });
- });
-
- it('should edit first entry', () => {
- cy.visit('/portfolio/groot');
- cy.get('.project-entry__paragraph:first-of-type').contains('Kill Thanos');
- cy.get('.project-entry:first-of-type').within(() => {
- cy.get('[data-cy=project-entry-more]').click();
- cy.get('[data-cy=edit-project-entry]').click();
- });
- cy.get('[data-cy=activity-input]').within(() => {
- cy.get('[data-cy=text-form-input]').clear().type('Defeat Thanos');
- });
- cy.get('[data-cy=modal-save-button]').click();
- cy.get('.project-entry__paragraph:first-of-type').contains('Defeat Thanos');
- });
-
- it('should delete the last entry', () => {
- cy.visit('/portfolio/groot');
-
- cy.get('.project-entry').should('have.length', 1);
-
- cy.get('.project-entry:last-of-type').within(() => {
- cy.get('[data-cy=project-entry-more]').click();
- cy.get('[data-cy=delete-project-entry]').click();
- });
-
- cy.get('.project-entry').should('have.length', 0);
- });
-});
From 3f5d70587720b0cc2d3a5f40e43d0338b5744935 Mon Sep 17 00:00:00 2001
From: Ramon Wenger
Date: Tue, 31 Aug 2021 21:22:45 +0200
Subject: [PATCH 02/28] Add portfolio onboarding page
---
.../frontend/portfolio/projects-page.spec.js | 2 +-
.../illustrations/PortfolioIllustration.vue | 347 ++++++------
.../illustrations/RoomsIllustration.vue | 526 +++++++-----------
.../portfolio/PortfolioOnboarding.vue | 51 ++
.../src/components/rooms/RoomsOnboarding.vue | 19 +-
.../src/graphql/gql/queries/allProjects.gql | 6 +-
client/src/pages/portfolio/portfolio.vue | 35 +-
client/src/styles/_mixins.scss | 26 +
8 files changed, 471 insertions(+), 541 deletions(-)
create mode 100644 client/src/components/portfolio/PortfolioOnboarding.vue
diff --git a/client/cypress/integration/frontend/portfolio/projects-page.spec.js b/client/cypress/integration/frontend/portfolio/projects-page.spec.js
index 623f3d55..4c580227 100644
--- a/client/cypress/integration/frontend/portfolio/projects-page.spec.js
+++ b/client/cypress/integration/frontend/portfolio/projects-page.spec.js
@@ -22,7 +22,7 @@ describe('Projects page', () => {
cy.getByDataCy('page-title').should('contain', 'Portfolio');
cy.getByDataCy('portfolio-onboarding-illustration').should('exist');
cy.getByDataCy('portfolio-onboarding-subtitle').should('contain', 'Woran denken Sie gerade');
- cy.getByDataCy('portfolio-onboarding-text').should('contain', 'Hier können Sie Projekte erstellen.');
+ cy.getByDataCy('portfolio-onboarding-text').should('contain', 'Hier können Sie Projekte erstellen');
cy.getByDataCy('page-footer').should('not.exist');
cy.getByDataCy('create-project-button').should('exist');
});
diff --git a/client/src/components/illustrations/PortfolioIllustration.vue b/client/src/components/illustrations/PortfolioIllustration.vue
index a041a90d..fb20d18c 100644
--- a/client/src/components/illustrations/PortfolioIllustration.vue
+++ b/client/src/components/illustrations/PortfolioIllustration.vue
@@ -1,234 +1,201 @@
diff --git a/client/src/components/illustrations/RoomsIllustration.vue b/client/src/components/illustrations/RoomsIllustration.vue
index 25c124ed..7f90ba6c 100644
--- a/client/src/components/illustrations/RoomsIllustration.vue
+++ b/client/src/components/illustrations/RoomsIllustration.vue
@@ -1,370 +1,270 @@
diff --git a/client/src/components/portfolio/PortfolioOnboarding.vue b/client/src/components/portfolio/PortfolioOnboarding.vue
new file mode 100644
index 00000000..964ac7c8
--- /dev/null
+++ b/client/src/components/portfolio/PortfolioOnboarding.vue
@@ -0,0 +1,51 @@
+
+
+
Portfolio
+
+
Woran denken Sie gerade?
+
+ Hier können Sie Projekte erstellen, um Ihre Gedanken festzuhalten oder Ihre Arbeit zu dokumentieren.
+
+
+
Projekt erstellen
+
+
+
+
+
+
diff --git a/client/src/components/rooms/RoomsOnboarding.vue b/client/src/components/rooms/RoomsOnboarding.vue
index 13ca43ca..66fbf1f4 100644
--- a/client/src/components/rooms/RoomsOnboarding.vue
+++ b/client/src/components/rooms/RoomsOnboarding.vue
@@ -47,31 +47,18 @@
@import '~styles/helpers';
.rooms-onboarding {
- display: flex;
- width: 100vw;
- max-width: 800px;
- flex-direction: column;
- justify-self: center;
- margin: 0 auto;
- grid-column: 1 / -1;
- align-items: center;
+ @include onboarding-page;
&__heading {
margin-bottom: $large-spacing;
}
&__illustration {
- width: 400px;
- height: 320px;
- flex-shrink: 0;
- margin-bottom: $large-spacing;
+ @include onboarding-illustration;
}
&__text {
- @include regular-text;
- text-align: center;
- max-width: 500px;
- margin-bottom: $large-spacing;
+ @include onboarding-text;
}
}
diff --git a/client/src/graphql/gql/queries/allProjects.gql b/client/src/graphql/gql/queries/allProjects.gql
index 82f30894..b626a7ca 100644
--- a/client/src/graphql/gql/queries/allProjects.gql
+++ b/client/src/graphql/gql/queries/allProjects.gql
@@ -1,10 +1,6 @@
#import "../fragments/projectParts.gql"
query ProjectsQuery {
projects {
- edges {
- node {
- ...ProjectParts
- }
- }
+ ...ProjectParts
}
}
diff --git a/client/src/pages/portfolio/portfolio.vue b/client/src/pages/portfolio/portfolio.vue
index 3f956f83..4e4e410f 100644
--- a/client/src/pages/portfolio/portfolio.vue
+++ b/client/src/pages/portfolio/portfolio.vue
@@ -1,19 +1,23 @@
@@ -23,9 +27,11 @@
import ME_QUERY from '@/graphql/gql/queries/meQuery.gql';
import PROJECTS_QUERY from '@/graphql/gql/queries/allProjects.gql';
+ import PortfolioOnboarding from '@/components/portfolio/PortfolioOnboarding';
export default {
components: {
+ PortfolioOnboarding,
ProjectWidget,
AddProject
},
@@ -33,9 +39,6 @@
apollo: {
projects: {
query: PROJECTS_QUERY,
- update(data) {
- return this.$getRidOfEdges(data).projects;
- },
pollInterval: 5000,
},
me: {
diff --git a/client/src/styles/_mixins.scss b/client/src/styles/_mixins.scss
index 1c1b2480..4b6848b0 100644
--- a/client/src/styles/_mixins.scss
+++ b/client/src/styles/_mixins.scss
@@ -78,6 +78,7 @@
@content
}
}
+
@mixin light-border($border-position) {
border-#{$border-position}: 1px solid $color-silver;
}
@@ -188,3 +189,28 @@
border: 0;
min-height: 110px;
}
+
+@mixin onboarding-illustration {
+ width: 400px;
+ height: 320px;
+ flex-shrink: 0;
+ margin-bottom: $large-spacing;
+}
+
+@mixin onboarding-page {
+ display: flex;
+ width: 100vw;
+ max-width: 800px;
+ flex-direction: column;
+ justify-self: center;
+ margin: 0 auto;
+ grid-column: 1 / -1;
+ align-items: center;
+}
+
+@mixin onboarding-text {
+ @include regular-text;
+ text-align: center;
+ max-width: 500px;
+ margin-bottom: $large-spacing;
+}
From 96ed807b2b6051b7d8403148eef24b644763c480 Mon Sep 17 00:00:00 2001
From: Ramon Wenger
Date: Thu, 2 Sep 2021 09:36:17 +0200
Subject: [PATCH 03/28] Update schema for projects
---
.../frontend/portfolio/new-project.spec.js | 50 +++++++++----------
.../frontend/portfolio/project-entry.spec.js | 45 ++++++++---------
.../src/components/content-forms/TextForm.vue | 12 ++++-
server/portfolio/schema.py | 2 +-
server/schema.graphql | 12 +----
5 files changed, 57 insertions(+), 64 deletions(-)
diff --git a/client/cypress/integration/frontend/portfolio/new-project.spec.js b/client/cypress/integration/frontend/portfolio/new-project.spec.js
index 86c43062..2c7346af 100644
--- a/client/cypress/integration/frontend/portfolio/new-project.spec.js
+++ b/client/cypress/integration/frontend/portfolio/new-project.spec.js
@@ -1,35 +1,35 @@
+import {getMinimalMe} from '../../../support/helpers';
+
describe('New project', () => {
+ const MeQuery = getMinimalMe({isTeacher: false});
+ const schoolClass = MeQuery.me.selectedClass;
+
const operations = {
ProjectsQuery: {
- projects: {
- edges: [
- {
- node: {
- id: 'UHJvamVjdE5vZGU6NjY=',
- title: 'Some random title',
- appearance: 'blue',
- description: 'This description rocks',
- slug: 'some-random-title',
- objectives: 'Git gud',
- final: false,
- student: {
- firstName: 'Rachel',
- lastName: 'Green',
- id: 'VXNlck5vZGU6NQ==',
- avatarUrl: '',
- },
- entriesCount: 0,
- },
+ projects: [
+ {
+ id: 'UHJvamVjdE5vZGU6NjY=',
+ title: 'Some random title',
+ appearance: 'blue',
+ description: 'This description rocks',
+ slug: 'some-random-title',
+ objectives: 'Git gud',
+ final: false,
+ schoolClass,
+ student: {
+ firstName: 'Rachel',
+ lastName: 'Green',
+ id: 'VXNlck5vZGU6NQ==',
+ avatarUrl: '',
},
- ],
- },
- },
- MeQuery: {
- me: {},
+ entriesCount: 0,
+ },
+ ],
},
+ MeQuery,
AddProject: variables => ({
addProject: {
- project: Object.assign({}, variables.input.project),
+ project: Object.assign({}, variables.input.project, {schoolClass}),
errors: null,
__typename: 'AddProjectPayload',
},
diff --git a/client/cypress/integration/frontend/portfolio/project-entry.spec.js b/client/cypress/integration/frontend/portfolio/project-entry.spec.js
index a7ca7a85..98657fee 100644
--- a/client/cypress/integration/frontend/portfolio/project-entry.spec.js
+++ b/client/cypress/integration/frontend/portfolio/project-entry.spec.js
@@ -7,30 +7,26 @@ describe('Project Entry', () => {
},
},
ProjectsQuery: {
- projects: {
- edges: [{
- node: {
- id: 'UHJvamVjdE5vZGU6MzM=',
- title: 'Groot',
- appearance: 'red',
- 'description': 'I am Groot',
- 'slug': 'groot',
- 'objectives': 'Be Groot\nBe awesome',
- 'final': false,
- 'student': {
- 'firstName': 'Rachel',
- 'lastName': 'Green',
- 'id': 'VXNlck5vZGU6NQ==',
- 'avatarUrl': '',
- '__typename': 'UserNode',
- },
- 'entriesCount': 2,
- '__typename': 'ProjectNode',
+ projects: [
+ {
+ id: 'UHJvamVjdE5vZGU6MzM=',
+ title: 'Groot',
+ appearance: 'red',
+ 'description': 'I am Groot',
+ 'slug': 'groot',
+ 'objectives': 'Be Groot\nBe awesome',
+ 'final': false,
+ 'student': {
+ 'firstName': 'Rachel',
+ 'lastName': 'Green',
+ 'id': 'VXNlck5vZGU6NQ==',
+ 'avatarUrl': '',
+ '__typename': 'UserNode',
},
- '__typename': 'ProjectNodeEdge',
- }],
- '__typename': 'ProjectNodeConnection',
- },
+ 'entriesCount': 2,
+ '__typename': 'ProjectNode',
+ },
+ ],
},
ProjectQuery: {
'project': {
@@ -85,8 +81,7 @@ describe('Project Entry', () => {
}),
DeleteProjectEntry: {
deleteProjectEntry: {
- success: true,
- __typename: 'DeleteProjectEntryPayload',
+ success: true
},
},
};
diff --git a/client/src/components/content-forms/TextForm.vue b/client/src/components/content-forms/TextForm.vue
index 16856932..25992aec 100644
--- a/client/src/components/content-forms/TextForm.vue
+++ b/client/src/components/content-forms/TextForm.vue
@@ -11,7 +11,15 @@
diff --git a/client/src/components/portfolio/ProjectForm.vue b/client/src/components/portfolio/ProjectForm.vue
index 72d526fc..62bfcf2b 100644
--- a/client/src/components/portfolio/ProjectForm.vue
+++ b/client/src/components/portfolio/ProjectForm.vue
@@ -9,10 +9,6 @@
v-model="localProject.description"
label="Beschreibung"
type="textarea"/>
-
Date: Thu, 2 Sep 2021 09:55:03 +0200
Subject: [PATCH 05/28] Update test
---
.../frontend/portfolio/project-entry.spec.js | 10 +++++-----
client/src/components/portfolio/ProjectEntryForm.vue | 1 +
2 files changed, 6 insertions(+), 5 deletions(-)
diff --git a/client/cypress/integration/frontend/portfolio/project-entry.spec.js b/client/cypress/integration/frontend/portfolio/project-entry.spec.js
index 98657fee..05933c30 100644
--- a/client/cypress/integration/frontend/portfolio/project-entry.spec.js
+++ b/client/cypress/integration/frontend/portfolio/project-entry.spec.js
@@ -81,7 +81,7 @@ describe('Project Entry', () => {
}),
DeleteProjectEntry: {
deleteProjectEntry: {
- success: true
+ success: true,
},
},
};
@@ -113,7 +113,7 @@ describe('Project Entry', () => {
// cy.get('[data-cy=text-form-input]').type('Stay with Rocket\nMeet Quill');
// });
cy.getByDataCy('modal-title').should('contain', 'Beitrag erfassen');
- cy.getByDataCy('project-entry-input').should('exist');
+ cy.getByDataCy('text-form-input').should('exist');
cy.getByDataCy('use-template-button').should('exist').click();
cy.getByDataCy('upload-document-button').should('exist');
cy.getByDataCy('modal-save-button').click();
@@ -130,9 +130,9 @@ describe('Project Entry', () => {
cy.get('[data-cy=project-entry-more]').click();
cy.get('[data-cy=edit-project-entry]').click();
});
- cy.get('[data-cy=activity-input]').within(() => {
- cy.get('[data-cy=text-form-input]').clear().type('Defeat Thanos');
- });
+ cy.getByDataCy('activity-input').should('not.exist');
+
+ cy.getByDataCy('text-form-input').clear().type('Defeat Thanos');
cy.get('[data-cy=modal-save-button]').click();
cy.get('.project-entry__paragraph:first-of-type').contains('Defeat Thanos');
});
diff --git a/client/src/components/portfolio/ProjectEntryForm.vue b/client/src/components/portfolio/ProjectEntryForm.vue
index aba739a6..a92f4b7e 100644
--- a/client/src/components/portfolio/ProjectEntryForm.vue
+++ b/client/src/components/portfolio/ProjectEntryForm.vue
@@ -2,6 +2,7 @@
Beitrag erfassen
From 3951b3f90c88655f4b0cda2802b79723ecb107c4 Mon Sep 17 00:00:00 2001
From: Ramon Wenger
Date: Mon, 20 Sep 2021 20:33:59 +0200
Subject: [PATCH 06/28] Fix unit test
---
server/portfolio/tests/test_project_query.py | 74 +++++---------------
1 file changed, 19 insertions(+), 55 deletions(-)
diff --git a/server/portfolio/tests/test_project_query.py b/server/portfolio/tests/test_project_query.py
index 4ff1d7a4..18f637ad 100644
--- a/server/portfolio/tests/test_project_query.py
+++ b/server/portfolio/tests/test_project_query.py
@@ -3,6 +3,7 @@ from graphene.test import Client
from graphql_relay import to_global_id
from api.schema import schema
+from core.tests.base_test import SkillboxTestCase
from portfolio.factories import ProjectFactory
from portfolio.models import Project
from rooms.models import Room
@@ -11,27 +12,17 @@ from users.models import User, SchoolClass
from users.services import create_users
-class ProjectQuery(TestCase):
+class ProjectQuery(SkillboxTestCase):
def setUp(self):
- create_users()
- self.teacher = User.objects.get(username='teacher')
- self.teacher2 = User.objects.get(username='teacher2')
- self.student = User.objects.get(username='student1')
- self.student2 = User.objects.get(username='student2')
- school_class1 = SchoolClassFactory(users=[self.teacher, self.student])
+ self.createDefault()
+ school_class1 = SchoolClassFactory(users=[self.teacher, self.student1])
school_class2 = SchoolClassFactory(users=[self.teacher2, self.student2])
- self.project1 = ProjectFactory(student=self.student)
+
+ self.project1 = ProjectFactory(student=self.student1)
self.query = '''
query ProjectsQuery {
projects {
- edges {
- node {
- ...ProjectParts
- __typename
- }
- __typename
- }
- __typename
+ ...ProjectParts
}
}
@@ -49,72 +40,45 @@ class ProjectQuery(TestCase):
def test_should_see_own_projects(self):
self.assertEqual(Project.objects.count(), 1)
- request = RequestFactory().get('/')
- request.user = self.student
- self.client = Client(schema=schema, context_value=request)
- result = self.client.execute(self.query)
+ result = self.get_client(self.student1).execute(self.query)
self.assertIsNone(result.get('errors'))
- self.assertEqual(result.get('data').get('projects').get('edges')[0].get('node').get('title'), self.project1.title)
+ self.assertEqual(result.get('data').get('projects')[0].get('title'), self.project1.title)
def test_should_not_see_other_projects(self):
self.assertEqual(Project.objects.count(), 1)
- request = RequestFactory().get('/')
- request.user = self.student2
- self.client = Client(schema=schema, context_value=request)
- result = self.client.execute(self.query)
+ result = self.get_client(self.student2).execute(self.query)
self.assertIsNone(result.get('errors'))
- self.assertEqual(len(result.get('data').get('projects').get('edges')), 0)
+ self.assertEqual(len(result.get('data').get('projects')), 0)
def test_teacher_should_not_see_unfinished_projects(self):
- request = RequestFactory().get('/')
- request.user = self.teacher
- self.client = Client(schema=schema, context_value=request)
-
- result = self.client.execute(self.query)
+ result = self.get_client().execute(self.query)
self.assertIsNone(result.get('errors'))
- self.assertEqual(len(result.get('data').get('projects').get('edges')), 0)
-
- def test_teacher_should_only_see_finished_projects(self):
- self.project1.final = True
- self.assertEqual(Project.objects.count(), 1)
- request = RequestFactory().get('/')
- request.user = self.teacher
- self.client = Client(schema=schema, context_value=request)
-
- result = self.client.execute(self.query)
-
- self.assertIsNone(result.get('errors'))
- self.assertEqual(result.get('data').get('projects').get('edges')[0].get('node').get('title'),
- self.project1.title)
+ self.assertEqual(len(result.get('data').get('projects')), 0)
def test_teacher_should_only_see_finished_projects(self):
self.project1.final = True
self.project1.save()
self.assertEqual(Project.objects.count(), 1)
- request = RequestFactory().get('/')
- request.user = self.teacher
- self.client = Client(schema=schema, context_value=request)
- result = self.client.execute(self.query)
+ result = self.get_client().execute(self.query)
self.assertIsNone(result.get('errors'))
- self.assertEqual(result.get('data').get('projects').get('edges')[0].get('node').get('title'),
+ self.assertEqual(result.get('data').get('projects')[0].get('title'),
self.project1.title)
+
def test_other_teacher_should_not_see_projects(self):
self.project1.final = True
self.project1.save()
self.assertEqual(Project.objects.count(), 1)
- request = RequestFactory().get('/')
- request.user = self.teacher2
- self.client = Client(schema=schema, context_value=request)
- result = self.client.execute(self.query)
+
+ result = self.get_client(self.teacher2).execute(self.query)
self.assertIsNone(result.get('errors'))
- self.assertEqual(len(result.get('data').get('projects').get('edges')), 0)
+ self.assertEqual(len(result.get('data').get('projects')), 0)
From 8e4cffd28f80c73cc5681ee3ccd023d8368e50b7 Mon Sep 17 00:00:00 2001
From: Ramon Wenger
Date: Mon, 20 Sep 2021 21:02:27 +0200
Subject: [PATCH 07/28] Fix some cypress tests
---
client/cypress/fixtures/mocks.js | 2 +-
.../frontend/read-only/portfolio.spec.js | 22 ++++++++-----------
2 files changed, 10 insertions(+), 14 deletions(-)
diff --git a/client/cypress/fixtures/mocks.js b/client/cypress/fixtures/mocks.js
index 66338361..f66111a1 100644
--- a/client/cypress/fixtures/mocks.js
+++ b/client/cypress/fixtures/mocks.js
@@ -1,5 +1,5 @@
const selectedClass = {
- id: 'selectedClassId',
+ id: btoa('SchoolClassNode:selectedClassId'),
name: 'Moordale',
readOnly: false,
code: 'XXXX',
diff --git a/client/cypress/integration/frontend/read-only/portfolio.spec.js b/client/cypress/integration/frontend/read-only/portfolio.spec.js
index 0d0abb44..6f63cff4 100644
--- a/client/cypress/integration/frontend/read-only/portfolio.spec.js
+++ b/client/cypress/integration/frontend/read-only/portfolio.spec.js
@@ -3,20 +3,16 @@ import {getMinimalMe} from '../../../support/helpers';
const getOperations = ({readOnly = false, classReadOnly = false}) => ({
MeQuery: getMinimalMe({readOnly, classReadOnly}),
ProjectsQuery: {
- projects: {
- edges: [
- {
- node: {
- id: 'projectId',
- final: false,
- student: {
- id: btoa('PrivateUserNode:1'),
- },
- entriesCount: 3,
- },
+ projects: [
+ {
+ id: 'projectId',
+ final: false,
+ student: {
+ id: btoa('PrivateUserNode:1'),
},
- ],
- },
+ entriesCount: 3,
+ },
+ ],
},
});
From 1f18f0feeb4db1e90af02117b740af113cf44e2a Mon Sep 17 00:00:00 2001
From: Ramon Wenger
Date: Mon, 27 Sep 2021 14:19:08 +0200
Subject: [PATCH 08/28] Update project list on portfolio page
---
.../frontend/portfolio/projects-page.spec.js | 32 +++++--
.../src/components/portfolio/AddProject.vue | 2 +-
.../portfolio/CreateProjectButton.vue | 26 ++++++
.../src/components/portfolio/OwnerWidget.vue | 2 +-
.../portfolio/PortfolioOnboarding.vue | 17 +---
.../src/components/portfolio/ProjectList.vue | 40 +++++++++
.../components/portfolio/ProjectListItem.vue | 89 +++++++++++++++++++
.../src/components/rooms/EntryCountWidget.vue | 16 +++-
client/src/mixins/me.js | 3 +
client/src/pages/portfolio/portfolio.vue | 28 +++---
client/src/pages/portfolio/project.vue | 5 +-
client/src/router/portfolio.routes.js | 2 +-
12 files changed, 216 insertions(+), 46 deletions(-)
create mode 100644 client/src/components/portfolio/CreateProjectButton.vue
create mode 100644 client/src/components/portfolio/ProjectList.vue
create mode 100644 client/src/components/portfolio/ProjectListItem.vue
diff --git a/client/cypress/integration/frontend/portfolio/projects-page.spec.js b/client/cypress/integration/frontend/portfolio/projects-page.spec.js
index 4c580227..4de3df5d 100644
--- a/client/cypress/integration/frontend/portfolio/projects-page.spec.js
+++ b/client/cypress/integration/frontend/portfolio/projects-page.spec.js
@@ -10,9 +10,7 @@ describe('Projects page', () => {
const operations = {
MeQuery,
ProjectsQuery: {
- projects: {
- edges: [],
- },
+ projects: [],
},
};
cy.mockGraphqlOps({
@@ -24,16 +22,30 @@ describe('Projects page', () => {
cy.getByDataCy('portfolio-onboarding-subtitle').should('contain', 'Woran denken Sie gerade');
cy.getByDataCy('portfolio-onboarding-text').should('contain', 'Hier können Sie Projekte erstellen');
cy.getByDataCy('page-footer').should('not.exist');
- cy.getByDataCy('create-project-button').should('exist');
+ cy.getByDataCy('add-project-button').should('exist');
});
it('displays the project list', () => {
+ const projectTitle = 'What is love?';
+
const operations = {
MeQuery,
ProjectsQuery: {
- projects: {
- edges: [],
- },
+ projects: [
+ {
+ title: projectTitle,
+ student: {
+ firstName: 'Bilbo',
+ lastName: 'Baggins',
+ avatarUrl: 'data:image/png;base64,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'
+ },
+ entriesCount: 0,
+ entries: {
+ nodes: [],
+
+ }
+ }
+ ],
},
};
@@ -42,9 +54,11 @@ describe('Projects page', () => {
});
cy.visit('/portfolio');
cy.getByDataCy('page-title').should('contain', 'Portfolio');
- cy.getByDataCy('create-project-button').should('exist');
+ cy.getByDataCy('add-project-button').should('exist');
cy.getByDataCy('project-list').should('exist');
cy.getByDataCy('project').should('have.length', 1);
- cy.getByDataCy('project-owner').should('contain', 'Bilbo Baggins');
+ cy.getByDataCy('owner-name').should('contain', 'Bilbo Baggins');
+ cy.getByDataCy('project-title').should('contain', projectTitle);
+ cy.getByDataCy('entry-count').should('contain', 0);
});
});
diff --git a/client/src/components/portfolio/AddProject.vue b/client/src/components/portfolio/AddProject.vue
index 66fb4d8b..95e33f94 100644
--- a/client/src/components/portfolio/AddProject.vue
+++ b/client/src/components/portfolio/AddProject.vue
@@ -1,7 +1,7 @@
+ data-cy="add-project-widget"/>
+
+
diff --git a/client/src/components/portfolio/OwnerWidget.vue b/client/src/components/portfolio/OwnerWidget.vue
index aecf5fe0..8970a9c2 100644
--- a/client/src/components/portfolio/OwnerWidget.vue
+++ b/client/src/components/portfolio/OwnerWidget.vue
@@ -3,7 +3,7 @@
-
+
{{ name }}
diff --git a/client/src/components/portfolio/PortfolioOnboarding.vue b/client/src/components/portfolio/PortfolioOnboarding.vue
index 33567d58..76d9b9b7 100644
--- a/client/src/components/portfolio/PortfolioOnboarding.vue
+++ b/client/src/components/portfolio/PortfolioOnboarding.vue
@@ -15,26 +15,15 @@
Hier können Sie Projekte erstellen, um Ihre Gedanken festzuhalten oder Ihre Arbeit zu dokumentieren.
- Projekt erstellen
+
diff --git a/client/src/components/portfolio/ProjectList.vue b/client/src/components/portfolio/ProjectList.vue
new file mode 100644
index 00000000..742aac6d
--- /dev/null
+++ b/client/src/components/portfolio/ProjectList.vue
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
diff --git a/client/src/components/portfolio/ProjectListItem.vue b/client/src/components/portfolio/ProjectListItem.vue
new file mode 100644
index 00000000..c80641e2
--- /dev/null
+++ b/client/src/components/portfolio/ProjectListItem.vue
@@ -0,0 +1,89 @@
+
+
+
+ {{ project.title }}
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/client/src/components/rooms/EntryCountWidget.vue b/client/src/components/rooms/EntryCountWidget.vue
index 934b9899..c032e2bd 100644
--- a/client/src/components/rooms/EntryCountWidget.vue
+++ b/client/src/components/rooms/EntryCountWidget.vue
@@ -1,7 +1,7 @@
- {{ entryCount }} {{ entryCount === 1 ? 'Beitrag' : 'Beiträge' }}
+ {{ entryCount }} {{ entryCount === 1 ? 'Beitrag' : 'Beiträge' }}
@@ -9,11 +9,19 @@
import Cards from '@/components/icons/Cards.vue';
export default {
- props: ['entryCount'],
+ props: {
+ entryCount: {
+ type: Number,
+ },
+ verbose: {
+ type: Boolean,
+ default: true,
+ },
+ },
components: {
- Cards
- }
+ Cards,
+ },
};
diff --git a/client/src/mixins/me.js b/client/src/mixins/me.js
index 87284aec..04203cf3 100644
--- a/client/src/mixins/me.js
+++ b/client/src/mixins/me.js
@@ -34,6 +34,9 @@ export default {
canManageContent() {
return this.me.isTeacher;
},
+ isReadOnly() {
+ return this.me.readOnly || this.me.selectedClass.readOnly;
+ },
currentClassName() {
let currentClass = this.me.schoolClasses.find(schoolClass => {
return schoolClass.id === this.me.selectedClass.id;
diff --git a/client/src/pages/portfolio/portfolio.vue b/client/src/pages/portfolio/portfolio.vue
index 4e4e410f..184a01ec 100644
--- a/client/src/pages/portfolio/portfolio.vue
+++ b/client/src/pages/portfolio/portfolio.vue
@@ -2,18 +2,12 @@
@@ -28,9 +22,13 @@
import ME_QUERY from '@/graphql/gql/queries/meQuery.gql';
import PROJECTS_QUERY from '@/graphql/gql/queries/allProjects.gql';
import PortfolioOnboarding from '@/components/portfolio/PortfolioOnboarding';
+ import CreateProjectButton from '@/components/portfolio/CreateProjectButton';
+ import ProjectList from '@/components/portfolio/ProjectList';
export default {
components: {
+ ProjectList,
+ CreateProjectButton,
PortfolioOnboarding,
ProjectWidget,
AddProject
@@ -70,15 +68,21 @@
@supports (display: grid) {
display: grid;
grid-template-columns: minmax(min-content, 840px);
+ grid-template-rows: auto;
}
grid-row-gap: 30px;
- grid-auto-rows: 225px;
+ grid-auto-rows: auto;
max-width: 840px;
width: 100vw;
/*justify-self: center;*/
box-sizing: border-box;
padding: $large-spacing $medium-spacing;
+ &__create-button {
+ display: inline-flex;
+ width: 150px;
+ }
+
&__page {
display: flex;
flex-direction: column;
diff --git a/client/src/pages/portfolio/project.vue b/client/src/pages/portfolio/project.vue
index 0220d58e..b46305ac 100644
--- a/client/src/pages/portfolio/project.vue
+++ b/client/src/pages/portfolio/project.vue
@@ -123,12 +123,9 @@
diff --git a/client/src/components/portfolio/NewProjectEntryWizard.vue b/client/src/components/portfolio/NewProjectEntryWizard.vue
index 53931b18..c73f80e0 100644
--- a/client/src/components/portfolio/NewProjectEntryWizard.vue
+++ b/client/src/components/portfolio/NewProjectEntryWizard.vue
@@ -19,7 +19,7 @@
data() {
return {
projectEntry: {
- content: '',
+ description: '',
documentUrl: ''
}
};
diff --git a/client/src/components/portfolio/ProjectEntryForm.vue b/client/src/components/portfolio/ProjectEntryForm.vue
index dce6209c..d21d8c5c 100644
--- a/client/src/components/portfolio/ProjectEntryForm.vue
+++ b/client/src/components/portfolio/ProjectEntryForm.vue
@@ -7,13 +7,28 @@
Beitrag erfassen
'' ? {
- url: this.localProjectEntry.documentUrl,
- } : {};
- },
- content() {
- return {
- text: this.localProjectEntry.content,
- };
- },
- },
-
methods: {
setDocumentUrl(url) {
this.localProjectEntry.documentUrl = url;
},
+ useTemplate() {
+ this.localProjectEntry.description = `${this.localProjectEntry.description}${PROJECT_ENTRY_TEMPLATE}`;
+ },
},
};
@@ -82,10 +98,46 @@
.project-entry-modal {
display: flex;
+ flex-direction: column;
+
+ &__form-field {
+ @include inputstyle;
+ padding: 0;
+ display: flex;
+ flex-direction: column;
+ grid-template-rows: auto 1rem;
+ grid-template-columns: 1fr 1fr;
+ width: 100%;
+ }
+
+ &__textarea {
+ @include auto-grow;
+ border: 0;
+ min-height: 400px;
+ padding: $medium-spacing;
+ }
+
+ &__buttons {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ padding: $medium-spacing;
+ }
+
+ &__button {
+ @include regular-text;
+
+ &--template {
+ }
+
+ &--document {
+ }
+ }
&__heading {
@include heading-3;
margin-bottom: 0;
}
+
}
+
diff --git a/client/src/components/ui/ButtonWithIconAndText.vue b/client/src/components/ui/ButtonWithIconAndText.vue
new file mode 100644
index 00000000..5a4cdb1f
--- /dev/null
+++ b/client/src/components/ui/ButtonWithIconAndText.vue
@@ -0,0 +1,51 @@
+
+
+
+ {{ text }}
+
+
+
+
+
+
diff --git a/client/src/components/ui/file-upload/FileUpload.vue b/client/src/components/ui/file-upload/FileUpload.vue
new file mode 100644
index 00000000..09205266
--- /dev/null
+++ b/client/src/components/ui/file-upload/FileUpload.vue
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/client/src/components/ui/file-upload/SimpleFileUpload.vue b/client/src/components/ui/file-upload/SimpleFileUpload.vue
new file mode 100644
index 00000000..c6a3c8ad
--- /dev/null
+++ b/client/src/components/ui/file-upload/SimpleFileUpload.vue
@@ -0,0 +1,71 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/client/src/components/ui/file-upload/SimpleFileUploadHiddenInput.vue b/client/src/components/ui/file-upload/SimpleFileUploadHiddenInput.vue
new file mode 100644
index 00000000..c8109914
--- /dev/null
+++ b/client/src/components/ui/file-upload/SimpleFileUploadHiddenInput.vue
@@ -0,0 +1,27 @@
+
+
+
+
+
diff --git a/client/src/components/ui/file-upload/SimpleFileUploadIcon.vue b/client/src/components/ui/file-upload/SimpleFileUploadIcon.vue
new file mode 100644
index 00000000..d7ed76c4
--- /dev/null
+++ b/client/src/components/ui/file-upload/SimpleFileUploadIcon.vue
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
diff --git a/client/src/components/ui/file-upload/SimpleFileUploadIconAndText.vue b/client/src/components/ui/file-upload/SimpleFileUploadIconAndText.vue
new file mode 100644
index 00000000..7f43c1f9
--- /dev/null
+++ b/client/src/components/ui/file-upload/SimpleFileUploadIconAndText.vue
@@ -0,0 +1,13 @@
+
+
+
+
+
diff --git a/client/src/components/SimpleFileUpload.vue b/client/src/components/ui/file-upload/SimpleFileUploadWithIcon.vue
similarity index 52%
rename from client/src/components/SimpleFileUpload.vue
rename to client/src/components/ui/file-upload/SimpleFileUploadWithIcon.vue
index aa5058db..de4f69e6 100644
--- a/client/src/components/SimpleFileUpload.vue
+++ b/client/src/components/ui/file-upload/SimpleFileUploadWithIcon.vue
@@ -1,41 +1,27 @@
-
-
-
-
+ @click.native="clickUploadCare"/>
+
+
diff --git a/client/src/components/portfolio/EditProject.vue b/client/src/components/portfolio/EditProject.vue
index 44314e9a..cc0ec956 100644
--- a/client/src/components/portfolio/EditProject.vue
+++ b/client/src/components/portfolio/EditProject.vue
@@ -27,7 +27,6 @@
id: project.id,
title: project.title,
description: project.description,
- appearance: project.appearance,
objectives: project.objectives
}
}
diff --git a/client/src/components/portfolio/ProjectForm.vue b/client/src/components/portfolio/ProjectForm.vue
index 62bfcf2b..67da6375 100644
--- a/client/src/components/portfolio/ProjectForm.vue
+++ b/client/src/components/portfolio/ProjectForm.vue
@@ -9,10 +9,6 @@
v-model="localProject.description"
label="Beschreibung"
type="textarea"/>
-
@@ -39,14 +41,13 @@
UserWidget,
Logo,
CurrentClass,
- Hamburger
+ Hamburger,
},
};
From 348e9198b67145e92810144fdc2806a1e2add69d Mon Sep 17 00:00:00 2001
From: Ramon Wenger
Date: Sun, 10 Oct 2021 21:53:54 +0200
Subject: [PATCH 23/28] Add share link to project page
---
.../frontend/portfolio/project-page.spec.js | 1 -
client/src/components/icons/ShareIcon.vue | 10 ++++
.../components/portfolio/ProjectActions.vue | 55 ++++++++-----------
client/src/components/portfolio/ShareLink.vue | 43 +++++++++++++++
client/src/components/ui/WidgetPopover.vue | 8 ++-
.../src/graphql/gql/queries/projectQuery.gql | 1 -
.../src/mixins/update-project-share-state.js | 32 +++++++++++
client/src/pages/portfolio/project.vue | 50 ++++++++++++-----
8 files changed, 150 insertions(+), 50 deletions(-)
create mode 100644 client/src/components/icons/ShareIcon.vue
create mode 100644 client/src/components/portfolio/ShareLink.vue
create mode 100644 client/src/mixins/update-project-share-state.js
diff --git a/client/cypress/integration/frontend/portfolio/project-page.spec.js b/client/cypress/integration/frontend/portfolio/project-page.spec.js
index e3e469e3..fefaff9e 100644
--- a/client/cypress/integration/frontend/portfolio/project-page.spec.js
+++ b/client/cypress/integration/frontend/portfolio/project-page.spec.js
@@ -101,7 +101,6 @@ describe('Project Page', () => {
cy.getByDataCy('project-actions').click();
cy.getByDataCy('delete-project').should('exist');
cy.getByDataCy('edit-project').should('exist');
- cy.getByDataCy('share-project').should('exist');
});
describe('Project Entry', () => {
diff --git a/client/src/components/icons/ShareIcon.vue b/client/src/components/icons/ShareIcon.vue
new file mode 100644
index 00000000..0a04014f
--- /dev/null
+++ b/client/src/components/icons/ShareIcon.vue
@@ -0,0 +1,10 @@
+
+
+
diff --git a/client/src/components/portfolio/ProjectActions.vue b/client/src/components/portfolio/ProjectActions.vue
index a515cd2b..62a16ca2 100644
--- a/client/src/components/portfolio/ProjectActions.vue
+++ b/client/src/components/portfolio/ProjectActions.vue
@@ -23,14 +23,14 @@
Projekt teilen
+ @click="updateProjectShareState(id, true)">Projekt teilen
Projekt nicht mehr teilen
+ @click="updateProjectShareState(id, false)">Projekt nicht mehr teilen
@@ -42,12 +42,27 @@ import Ellipses from '@/components/icons/Ellipses.vue';
import WidgetPopover from '@/components/ui/WidgetPopover';
import DELETE_PROJECT_MUTATION from '@/graphql/gql/mutations/deleteProject.gql';
-import PROJECT_QUERY from '@/graphql/gql/queries/projectQuery.gql';
import PROJECTS_QUERY from '@/graphql/gql/queries/allProjects.gql';
-import UPDATE_PROJECT_SHARED_STATE_MUTATION from '@/graphql/gql/mutations/updateProjectSharedState.gql';
+
+import updateProjectShareState from '@/mixins/update-project-share-state';
export default {
- props: ['id', 'final'],
+ props: {
+ id: {
+ type: String,
+ default: '',
+ },
+ final: {
+ type: Boolean,
+ default: false,
+ },
+ shareButtons: {
+ type: Boolean,
+ default: true,
+ },
+ },
+
+ mixins: [updateProjectShareState],
components: {
Ellipses,
@@ -89,32 +104,6 @@ export default {
this.$router.push('/portfolio');
});
},
- updateShareState(id, state) {
- const project = this;
- this.$apollo.mutate({
- mutation: UPDATE_PROJECT_SHARED_STATE_MUTATION,
- variables: {
- input: {
- id: this.id,
- shared: state,
- },
- },
- update(store, {data: {updateProjectSharedState: {shared, errors}}}) {
- if (!errors) {
- const query = PROJECT_QUERY;
- const variables = {
- id: project.id,
- };
- const data = store.readQuery({query, variables});
-
- if (data) {
- data.project.final = shared;
- store.writeQuery({query, variables, data});
- }
- }
- },
- });
- },
},
};
diff --git a/client/src/components/portfolio/ShareLink.vue b/client/src/components/portfolio/ShareLink.vue
new file mode 100644
index 00000000..124f1c0b
--- /dev/null
+++ b/client/src/components/portfolio/ShareLink.vue
@@ -0,0 +1,43 @@
+
+
+
+
+ Mit Lehrperson teilen
+ Nicht mehr teilen
+
+
+
+
+
+
+
diff --git a/client/src/components/ui/WidgetPopover.vue b/client/src/components/ui/WidgetPopover.vue
index 340a5beb..93e3a1c0 100644
--- a/client/src/components/ui/WidgetPopover.vue
+++ b/client/src/components/ui/WidgetPopover.vue
@@ -11,7 +11,13 @@