Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
7485bf2
feat: add implementation plan and initial project setup for AI Event …
lennyStreavent Jul 8, 2025
517b4b4
feat: complete Phase 2 of the design system implementation
lennyStreavent Jul 8, 2025
f1dbb60
feat: complete Phase 3 implementation of core services and models
lennyStreavent Jul 8, 2025
c80e90b
feat: complete Phase 4 implementation of authentication system
lennyStreavent Jul 8, 2025
5d9a526
feat: implement Phase 6 with core user-facing features and enhancements
lennyStreavent Jul 8, 2025
9aca150
feat: complete Phase 8 implementation with mock API system and enhanc…
lennyStreavent Jul 8, 2025
f9e5468
feat: enhance authentication and dashboard UI with floating elements …
lennyStreavent Jul 9, 2025
8b4486f
feat: revamp concepts list and create concept components with enhance…
lennyStreavent Jul 9, 2025
9fbc644
fix: update configuration and remove unused mock data
lennyStreavent Jul 9, 2025
dc5660a
feat: added some basic unit testing
lennyStreavent Jul 9, 2025
35a371c
feat: add unit tests for authentication and guest guards, and interce…
lennyStreavent Jul 9, 2025
f53b366
feat: add unit tests for concepts list, create concept, dashboard, an…
lennyStreavent Jul 9, 2025
d4e08c4
feat: add unit tests for concept detail, profile, chat interface, and…
lennyStreavent Jul 9, 2025
0b0aca5
feat: update angular.json and remove implementation plan
lennyStreavent Jul 9, 2025
f291385
feat: enhance API service and add new chat and document services
lennyStreavent Jul 9, 2025
7d50135
refactor: streamline concept service and state management
lennyStreavent Jul 9, 2025
e592657
feat: enhance concept detail UI with improved layout and loading state
lennyStreavent Jul 9, 2025
928d290
feat: enhance concept detail component with accordion layout and them…
lennyStreavent Jul 9, 2025
0d8fd9b
refactor: simplify chat interface layout and enhance styling
lennyStreavent Jul 9, 2025
1e87fca
feat: enhance chat service and concept detail component with suggesti…
lennyStreavent Jul 9, 2025
544a6ba
feat: refactor concept detail component with modular sections for imp…
lennyStreavent Jul 9, 2025
eb037d8
Merge branch 'dev' into feat/client-v1
lennyStreavent Jul 9, 2025
680c24f
Merge branch 'dev' into feat/client-v1
sfdamm Jul 10, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions client/angular.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
{
"type": "anyComponentStyle",
"maximumWarning": "4kB",
"maximumError": "8kB"
"maximumError": "12kB"
}
],
"outputHashing": "all"
Expand Down Expand Up @@ -93,7 +93,10 @@
"styles": [
"src/styles.scss"
],
"scripts": []
"scripts": [],
"codeCoverageExclude": [
"src/app/mocks/**/*"
]
}
}
}
Expand Down
23 changes: 23 additions & 0 deletions client/cypress.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { defineConfig } from 'cypress';

export default defineConfig({
e2e: {
baseUrl: 'http://localhost:4200',
viewportWidth: 1280,
viewportHeight: 720,
video: false,
screenshotOnRunFailure: false,
setupNodeEvents(on, config) {
// implement node event listeners here
},
specPattern: 'cypress/e2e/**/*.cy.{js,jsx,ts,tsx}',
supportFile: 'cypress/support/e2e.ts'
},
component: {
devServer: {
framework: 'angular',
bundler: 'webpack',
},
specPattern: '**/*.cy.ts'
}
});
290 changes: 290 additions & 0 deletions client/cypress/e2e/auth-flow.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,290 @@
/// <reference types="cypress" />

describe('Authentication Flow', () => {
beforeEach(() => {
// Clear any existing authentication state
cy.clearLocalStorage();
cy.clearCookies();
});

describe('Login Page', () => {
beforeEach(() => {
cy.visit('/auth/login');
});

it('should display login form', () => {
cy.get('[data-cy=login-form]').should('be.visible');
cy.get('[data-cy=email-input]').should('be.visible');
cy.get('[data-cy=password-input]').should('be.visible');
cy.get('[data-cy=login-button]').should('be.visible');
});

it('should show validation errors for empty form submission', () => {
cy.get('[data-cy=login-button]').click();

cy.get('[data-cy=email-error]').should('contain', 'Email is required');
cy.get('[data-cy=password-error]').should('contain', 'Password is required');
});

it('should show validation error for invalid email format', () => {
cy.get('[data-cy=email-input]').type('invalid-email');
cy.get('[data-cy=email-input]').blur();

cy.get('[data-cy=email-error]').should('contain', 'Please enter a valid email');
});

it('should show validation error for short password', () => {
cy.get('[data-cy=password-input]').type('123');
cy.get('[data-cy=password-input]').blur();

cy.get('[data-cy=password-error]').should('contain', 'Password must be at least 6 characters');
});

it('should toggle password visibility', () => {
cy.get('[data-cy=password-input]').should('have.attr', 'type', 'password');

cy.get('[data-cy=password-toggle]').click();
cy.get('[data-cy=password-input]').should('have.attr', 'type', 'text');

cy.get('[data-cy=password-toggle]').click();
cy.get('[data-cy=password-input]').should('have.attr', 'type', 'password');
});

it('should login successfully with valid credentials', () => {
cy.get('[data-cy=email-input]').type('demo@concepter.com');
cy.get('[data-cy=password-input]').type('demo123');
cy.get('[data-cy=login-button]').click();

// Should redirect to dashboard
cy.url().should('include', '/dashboard');

// Should show user info in header
cy.get('[data-cy=user-menu]').should('be.visible');
});

it('should show error message for invalid credentials', () => {
cy.get('[data-cy=email-input]').type('wrong@example.com');
cy.get('[data-cy=password-input]').type('wrongpassword');
cy.get('[data-cy=login-button]').click();

cy.get('[data-cy=error-message]').should('be.visible');
cy.get('[data-cy=error-message]').should('contain', 'Invalid credentials');
});

it('should remember user preference', () => {
cy.get('[data-cy=email-input]').type('demo@concepter.com');
cy.get('[data-cy=password-input]').type('demo123');
cy.get('[data-cy=remember-me]').check();
cy.get('[data-cy=login-button]').click();

// Verify remember me is checked
cy.get('[data-cy=remember-me]').should('be.checked');
});

it('should navigate to register page', () => {
cy.get('[data-cy=register-link]').click();
cy.url().should('include', '/auth/register');
});
});

describe('Registration Page', () => {
beforeEach(() => {
cy.visit('/auth/register');
});

it('should display registration form', () => {
cy.get('[data-cy=register-form]').should('be.visible');
cy.get('[data-cy=first-name-input]').should('be.visible');
cy.get('[data-cy=last-name-input]').should('be.visible');
cy.get('[data-cy=email-input]').should('be.visible');
cy.get('[data-cy=password-input]').should('be.visible');
cy.get('[data-cy=register-button]').should('be.visible');
});

it('should complete registration flow', () => {
// Fill basic information
cy.get('[data-cy=first-name-input]').type('John');
cy.get('[data-cy=last-name-input]').type('Doe');
cy.get('[data-cy=email-input]').type('john.doe@example.com');
cy.get('[data-cy=password-input]').type('password123');

// Fill preferences if using stepper
cy.get('[data-cy=next-button]').click();
cy.get('[data-cy=event-format-select]').click();
cy.get('[data-cy=hybrid-option]').click();
cy.get('[data-cy=industry-select]').click();
cy.get('[data-cy=technology-option]').click();

cy.get('[data-cy=register-button]').click();

// Should redirect to dashboard after successful registration
cy.url().should('include', '/dashboard');
cy.get('[data-cy=user-menu]').should('contain', 'John Doe');
});

it('should navigate back to login page', () => {
cy.get('[data-cy=login-link]').click();
cy.url().should('include', '/auth/login');
});
});

describe('Authentication State', () => {
it('should redirect unauthenticated users to login', () => {
cy.visit('/dashboard');
cy.url().should('include', '/auth/login');

cy.visit('/concepts');
cy.url().should('include', '/auth/login');

cy.visit('/profile');
cy.url().should('include', '/auth/login');
});

it('should allow access to protected routes after login', () => {
// Login first
cy.visit('/auth/login');
cy.get('[data-cy=email-input]').type('demo@concepter.com');
cy.get('[data-cy=password-input]').type('demo123');
cy.get('[data-cy=login-button]').click();

// Should be able to access protected routes
cy.visit('/dashboard');
cy.url().should('include', '/dashboard');

cy.visit('/concepts');
cy.url().should('include', '/concepts');

cy.visit('/profile');
cy.url().should('include', '/profile');
});

it('should persist authentication across browser refresh', () => {
// Login
cy.visit('/auth/login');
cy.get('[data-cy=email-input]').type('demo@concepter.com');
cy.get('[data-cy=password-input]').type('demo123');
cy.get('[data-cy=login-button]').click();

// Refresh the page
cy.reload();

// Should still be authenticated
cy.url().should('include', '/dashboard');
cy.get('[data-cy=user-menu]').should('be.visible');
});
});

describe('Logout', () => {
beforeEach(() => {
// Login before each logout test
cy.visit('/auth/login');
cy.get('[data-cy=email-input]').type('demo@concepter.com');
cy.get('[data-cy=password-input]').type('demo123');
cy.get('[data-cy=login-button]').click();
cy.url().should('include', '/dashboard');
});

it('should logout successfully', () => {
cy.get('[data-cy=user-menu]').click();
cy.get('[data-cy=logout-button]').click();

// Should redirect to login page
cy.url().should('include', '/auth/login');

// Should not be able to access protected routes
cy.visit('/dashboard');
cy.url().should('include', '/auth/login');
});

it('should clear authentication state after logout', () => {
cy.get('[data-cy=user-menu]').click();
cy.get('[data-cy=logout-button]').click();

// Check that localStorage is cleared
cy.window().then((window) => {
expect(window.localStorage.getItem('access_token')).to.be.null;
expect(window.localStorage.getItem('current_user')).to.be.null;
});
});
});

describe('Loading States', () => {
it('should show loading spinner during login', () => {
cy.visit('/auth/login');

// Intercept the login API call to add delay
cy.intercept('POST', '/api/auth/login', { delay: 1000 }).as('loginRequest');

cy.get('[data-cy=email-input]').type('demo@concepter.com');
cy.get('[data-cy=password-input]').type('demo123');
cy.get('[data-cy=login-button]').click();

// Should show loading spinner
cy.get('[data-cy=loading-spinner]').should('be.visible');

cy.wait('@loginRequest');

// Loading spinner should disappear
cy.get('[data-cy=loading-spinner]').should('not.exist');
});
});

describe('Error Handling', () => {
it('should handle network errors gracefully', () => {
cy.visit('/auth/login');

// Simulate network error
cy.intercept('POST', '/api/auth/login', { forceNetworkError: true }).as('loginError');

cy.get('[data-cy=email-input]').type('demo@concepter.com');
cy.get('[data-cy=password-input]').type('demo123');
cy.get('[data-cy=login-button]').click();

// Should show error message
cy.get('[data-cy=error-message]').should('be.visible');
cy.get('[data-cy=error-message]').should('contain', 'Network error');
});

it('should handle server errors gracefully', () => {
cy.visit('/auth/login');

// Simulate server error
cy.intercept('POST', '/api/auth/login', {
statusCode: 500,
body: { message: 'Internal server error' }
}).as('serverError');

cy.get('[data-cy=email-input]').type('demo@concepter.com');
cy.get('[data-cy=password-input]').type('demo123');
cy.get('[data-cy=login-button]').click();

// Should show error message
cy.get('[data-cy=error-message]').should('be.visible');
cy.get('[data-cy=error-message]').should('contain', 'server error');
});
});

describe('Accessibility', () => {
it('should be keyboard navigable', () => {
cy.visit('/auth/login');

// Tab through form elements
cy.get('body').type('{tab}');
cy.focused().should('have.attr', 'formControlName', 'email');

cy.focused().type('{tab}');
cy.focused().should('have.attr', 'formControlName', 'password');

cy.focused().type('{tab}');
cy.focused().should('have.attr', 'type', 'submit');
});

it('should have proper ARIA labels', () => {
cy.visit('/auth/login');

cy.get('[data-cy=email-input]').should('have.attr', 'aria-label');
cy.get('[data-cy=password-input]').should('have.attr', 'aria-label');
cy.get('[data-cy=login-button]').should('have.attr', 'aria-label');
});
});
});
11 changes: 9 additions & 2 deletions client/nginx.conf
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,19 @@ server {
add_header Cache-Control "no-cache";
}

# Cache static assets
location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
# Cache static assets (Angular generates unique filenames with hashes)
location ~* \.(jpg|jpeg|png|gif|ico)$ {
root /usr/share/nginx/html;
expires 1y;
add_header Cache-Control "public, no-transform";
}

# Cache JS/CSS with shorter duration for development
location ~* \.(css|js)$ {
root /usr/share/nginx/html;
expires 1h;
add_header Cache-Control "public, no-transform";
}

# Error handling
error_page 404 /index.html;
Expand Down
Loading
Loading