From e8cf7e8eff9445df4c454eb94745cfc2ae0e3182 Mon Sep 17 00:00:00 2001 From: Alexander Date: Fri, 21 Mar 2025 10:58:24 +0000 Subject: [PATCH 01/34] fix: added gratitude to Action interface --- GRATITUDE_API.md | 208 +++++++++++++++++++++++++++++++++++++++++++ src/types/entries.ts | 10 ++- 2 files changed, 214 insertions(+), 4 deletions(-) create mode 100644 GRATITUDE_API.md diff --git a/GRATITUDE_API.md b/GRATITUDE_API.md new file mode 100644 index 0000000..a77b922 --- /dev/null +++ b/GRATITUDE_API.md @@ -0,0 +1,208 @@ +# Gratitude API + +## Overview + +The Gratitude feature allows users to send appreciation messages to others when marking actions as completed. This functionality enhances the collaborative nature of the application by enabling users to acknowledge contributions and express thanks to colleagues, managers, or team members. + +## API Endpoints + +### 1. Send Gratitude Email Endpoint + +- **URL**: `/api/gratitude/send` +- **Method**: POST +- **Content-Type**: application/json +- **Credentials**: include +- **Request Body**: + ```json + { + "statementId": "string", // ID of the statement containing the action + "actionId": "string", // ID of the specific action + "message": "string", // The gratitude message to send + "recipientEmail": "string", // Email address of the recipient + "recipientName": "string" // Optional name of the recipient + } + ``` +- **Expected Response**: + + - Success: + ```json + { + "success": true, + "message": "Gratitude email sent successfully", + "id": "string" // Unique ID for the sent message (for tracking) + } + ``` + - Error: + ```json + { + "success": false, + "message": "Error description" + } + ``` + +- **Backend Behavior**: + - Validate all inputs, especially the email format + - Compose an email using an appropriate template + - Include the gratitude message in the email + - Send the email to the recipient + - Return success confirmation with a unique ID + - Handle failures gracefully with meaningful error messages + +### 2. Mark Gratitude Sent Endpoint + +- **URL**: `/api/gratitude/mark/{statementId}/{actionId}` +- **Method**: POST +- **Content-Type**: application/json +- **Credentials**: include +- **URL Parameters**: + - `statementId`: The ID of the statement + - `actionId`: The ID of the action +- **Request Body**: + ```json + { + "message": "string" // The gratitude message that was sent + } + ``` +- **Expected Response**: + + - Success: Return the updated Action object: + ```json + { + "id": "string", // Action ID + "creationDate": "string", // ISO date string + "byDate": "string", // ISO date string (can be empty) + "action": "string", // Action text + "completed": true, // Should be true for gratitude to be sent + "gratitudeSent": true, // Must be set to true + "gratitudeMessage": "string", // The message that was sent + "gratitudeSentDate": "string" // Current timestamp in ISO format + } + ``` + - Error: + ```json + { + "success": false, + "message": "Error description" + } + ``` + +- **Backend Behavior**: + - Verify the statement and action exist + - Update the action record with gratitude information + - Set the `gratitudeSent` flag to true + - Store the gratitude message + - Record the current timestamp as the sent date + - Return the complete updated action object + +## Feature Flow + +The Gratitude feature flow in the LIFT frontend consists of the following steps: + +1. **Initiating Gratitude** + + - User completes an action + - User clicks the dropdown menu for the action + - User selects "Send gratitude" option + - System displays the GratitudeModal + +2. **Composing the Message** + + - User enters recipient email + - User enters gratitude message + - System validates inputs + - User submits the form + +3. **Sending the Gratitude** + + - System calls the `/api/gratitude/send` endpoint + - Email is sent to the recipient + - System calls the `/api/gratitude/mark/{statementId}/{actionId}` endpoint + - Action is updated with gratitude information + +4. **Visual Feedback** + - System displays success confirmation + - Action UI updates to show gratitude was sent + - Dropdown menu options are updated + - Tooltips display gratitude information + +## Mock Implementation + +For development and testing purposes, the frontend includes a mock implementation: + +- Controlled by the environment variable `VITE_MOCK_EMAIL_SENDING` +- When enabled, simulates the API responses without real backend +- Logs details to console for debugging +- Includes realistic delays to simulate network latency + +To use the mock implementation: + +1. Set `VITE_MOCK_EMAIL_SENDING=true` in your environment +2. The frontend will use the mock implementations in `gratitudeApi.ts` +3. No actual emails will be sent, but the UI will behave as if they were + +## Security Considerations + +When implementing the Gratitude API endpoints, consider the following security aspects: + +1. **Input Validation** + + - Validate email formats + - Sanitize message content to prevent injection + - Validate that IDs exist and belong to the requesting user + +2. **Authorization** + + - Ensure users can only send gratitude for actions they have access to + - Implement proper authentication checks + - Verify user permissions before processing requests + +3. **Rate Limiting** + + - Implement rate limiting to prevent abuse + - Consider limits like maximum 10 emails per hour per user + - Add exponential backoff for repeated failures + +4. **Email Security** + + - Ensure proper email headers + - Prevent header injection + - Include unsubscribe options + - Follow email sending best practices + +5. **Logging** + - Log all gratitude emails sent for auditing + - Track failures and errors + - Monitor for unusual patterns + +## Testing the Integration + +When testing the integration between frontend and backend: + +1. **Verify Email Sending** + + - Test with valid and invalid email addresses + - Confirm emails are received by recipients + - Check email content and formatting + +2. **Test State Updates** + + - Ensure action records are properly updated + - Verify timestamps are accurate + - Confirm UI updates correctly after sending + +3. **Error Handling** + + - Test with various error scenarios + - Verify user-friendly error messages + - Ensure system recovers gracefully from failures + +4. **Edge Cases** + - Test with very long messages + - Test with special characters + - Test with high volume of requests + +## Conclusion + +The Gratitude API enhances the LIFT application by enabling users to express appreciation for completed actions. When implemented correctly, it creates a positive feedback loop within teams and reinforces collaborative behaviors. + +This document provides a comprehensive guide for backend developers to implement the required endpoints and behaviors to support the Gratitude feature in the LIFT frontend application. diff --git a/src/types/entries.ts b/src/types/entries.ts index aeff3bc..49d9a69 100644 --- a/src/types/entries.ts +++ b/src/types/entries.ts @@ -11,9 +11,11 @@ export interface Action { byDate: string; action: string; completed: boolean; - gratitudeSent?: boolean; - gratitudeMessage?: string; - gratitudeSentDate?: string; + gratitude?: { + sent?: boolean; + message?: string; + sentDate?: string; + }; } export interface Entry { @@ -44,7 +46,7 @@ export interface Verb { popularity: number; categories: string[]; color: string; - presentTenseForm?: string; // Optional field for verbs that need explicit present tense form + presentTenseForm?: string; // Optional field for verbs that need explicit present tense form } export interface SubjectData { From d72bf86b0a7127894bbcec7f565e46a96e71ad99 Mon Sep 17 00:00:00 2001 From: Alexander Date: Fri, 21 Mar 2025 11:46:04 +0000 Subject: [PATCH 02/34] fix: adjusted gratitudeApi to use new Action interface --- MIGRATION_GUIDE.md | 73 -- driver.js.md | 885 ++++++++++++++++++ package-lock.json | 6 + package.json | 1 + src/components/ui/tour/AppTour.tsx | 110 +++ src/data/setQuestions.json | 29 + src/data/statementsCategories.json | 4 + src/features/email/api/gratitudeApi.ts | 8 +- .../statements/components/ActionLine.tsx | 12 +- .../statements/components/QuestionCard.tsx | 2 +- .../statements/components/StatementList.tsx | 6 +- src/layouts/components/Header.tsx | 6 +- src/layouts/components/MainPage.tsx | 19 +- 13 files changed, 1072 insertions(+), 89 deletions(-) delete mode 100644 MIGRATION_GUIDE.md create mode 100644 driver.js.md create mode 100644 src/components/ui/tour/AppTour.tsx diff --git a/MIGRATION_GUIDE.md b/MIGRATION_GUIDE.md deleted file mode 100644 index a87e6b9..0000000 --- a/MIGRATION_GUIDE.md +++ /dev/null @@ -1,73 +0,0 @@ -# Project Structure Migration Guide - -## Directory Structure Changes - -We've restructured the project to follow a more organized, feature-based architecture. Here's a summary of what changed: - -### Feature-based Organization - -Files are now organized by feature, with each feature containing its own: -- Components -- Hooks -- Context -- API calls -- Types (when feature-specific) - -### New Directory Structure - -``` -src/ -├── assets/ # Images and static assets -├── components/ # Shared UI components -│ ├── ui/ # Base UI components (buttons, inputs, etc.) -│ ├── modals/ # Modal dialogs -│ └── shared/ # Other shared components -├── config/ # Application configuration -├── data/ # Static data files (JSON, etc.) -├── features/ # Feature-specific code -│ ├── auth/ # Authentication feature -│ ├── email/ # Email-related functionality -│ ├── questions/ # Questions management -│ ├── statements/ # Statements management -│ └── wizard/ # Statement wizard feature -├── layouts/ # Layout components -├── lib/ # Shared utilities -│ └── utils/ # Utility functions -├── providers/ # Context providers -├── routes/ # Route definitions -└── types/ # TypeScript type definitions -``` - -## Import Updates Required - -Due to the restructuring, imports in files need to be updated. Here are the general patterns to follow: - -### Old vs New Import Paths - -| Old Import Path | New Import Path | -|-----------------|-----------------| -| `../components/ui/...` | `../components/ui/...` (unchanged) | -| `../components/Header` | `../layouts/components/Header` | -| `../components/MainPage` | `../layouts/components/MainPage` | -| `../context/AuthContext` | `../features/auth/AuthContext` | -| `../context/AuthProvider` | `../features/auth/AuthProvider` | -| `../context/EntriesContext` | `../features/statements/context/EntriesContext` | -| `../context/EntriesProvider` | `../features/statements/context/EntriesProvider` | -| `../context/QuestionsContext` | `../providers/QuestionsContext` | -| `../context/QuestionsProvider` | `../providers/QuestionsProvider` | -| `../hooks/useAuth` | `../features/auth/hooks/useAuth` | -| `../hooks/useEntries` | `../features/statements/hooks/useEntries` | -| `../hooks/useQuestions` | `../features/questions/hooks/useQuestions` | -| `../api/authApi` | `../features/auth/api/authApi` | -| `../api/entriesApi` | `../features/statements/api/entriesApi` | -| `../api/emailApi` | `../features/email/api/emailApi` | -| `../utils/...` | `../lib/utils/...` | -| `../data/...` | `../data/...` | - -## Next Steps - -1. Update imports in all files -2. Run tests to ensure the restructuring doesn't break functionality -3. Update build scripts if needed to accommodate the new structure - -This restructuring will make the codebase more maintainable, easier to navigate, and better prepared for future growth. \ No newline at end of file diff --git a/driver.js.md b/driver.js.md new file mode 100644 index 0000000..91c0501 --- /dev/null +++ b/driver.js.md @@ -0,0 +1,885 @@ +Installation +Run one of the following commands to install the package: + +# Using npm + +npm install driver.js + +# Using pnpm + +pnpm install driver.js + +# Using yarn + +yarn add driver.js +Alternatively, you can use CDN and include the script in your HTML file: + + + +Start Using +Once installed, you can import the package in your project. The following example shows how to highlight an element: + +import { driver } from "driver.js"; +import "driver.js/dist/driver.css"; + +const driverObj = driver(); +driverObj.highlight({ +element: "#some-element", +popover: { +title: "Title", +description: "Description" +} +}); +Note on CDN +If you are using the CDN, you will have to use the package from the window object: + +const driver = window.driver.js.driver; + +const driverObj = driver(); + +driverObj.highlight({ +element: "#some-element", +popover: { +title: "Title", +description: "Description" +} +}); +Continue reading the Getting Started guide to learn more about the package. + +Basic Usage +Once installed, you can import and start using the library. There are several different configuration options available to customize the library. You can find more details about the options in the configuration section. Given below are the basic steps to get started. + +Here is a simple example of how to create a tour with multiple steps. + +Basic Tour Example + +import { driver } from "driver.js"; +import "driver.js/dist/driver.css"; + +const driverObj = driver({ +showProgress: true, +steps: [ +{ element: '.page-header', popover: { title: 'Title', description: 'Description' } }, +{ element: '.top-nav', popover: { title: 'Title', description: 'Description' } }, +{ element: '.sidebar', popover: { title: 'Title', description: 'Description' } }, +{ element: '.footer', popover: { title: 'Title', description: 'Description' } }, +] +}); + +driverObj.drive(); +Show me an Example +You can pass a single step configuration to the highlight method to highlight a single element. Given below is a simple example of how to highlight a single element. + +Highlighting a simple Element + +import { driver } from "driver.js"; +import "driver.js/dist/driver.css"; + +const driverObj = driver(); +driverObj.highlight({ +element: '#some-element', +popover: { +title: 'Title for the Popover', +description: 'Description for it', +}, +}); +Show me an Example +The same configuration passed to the highlight method can be used to create a tour. Given below is a simple example of how to create a tour with a single step. + +Examples above show the basic usage of the library. Find more details about the configuration options in the configuration section and the examples in the examples section. + +Configuration +Driver.js is built to be highly configurable. You can configure the driver globally, or per step. You can also configure the driver on the fly, while it’s running. + +Driver.js is written in TypeScript. Configuration options are mostly self-explanatory. Also, if you’re using an IDE like WebStorm or VSCode, you’ll get autocomplete and documentation for all the configuration options. + +Driver Configuration +You can configure the driver globally by passing the configuration object to the driver call or by using the setConfig method. Given below are some of the available configuration options. + +type Config = { +// Array of steps to highlight. You should pass +// this when you want to setup a product tour. +steps?: DriveStep[]; + +// Whether to animate the product tour. (default: true) +animate?: boolean; +// Overlay color. (default: black) +// This is useful when you have a dark background +// and want to highlight elements with a light +// background color. +overlayColor?: string; +// Whether to smooth scroll to the highlighted element. (default: false) +smoothScroll?: boolean; +// Whether to allow closing the popover by clicking on the backdrop. (default: true) +allowClose?: boolean; +// Opacity of the backdrop. (default: 0.5) +overlayOpacity?: number; +// Distance between the highlighted element and the cutout. (default: 10) +stagePadding?: number; +// Radius of the cutout around the highlighted element. (default: 5) +stageRadius?: number; + +// Whether to allow keyboard navigation. (default: true) +allowKeyboardControl?: boolean; + +// Whether to disable interaction with the highlighted element. (default: false) +// Can be configured at the step level as well +disableActiveInteraction?: boolean; + +// If you want to add custom class to the popover +popoverClass?: string; +// Distance between the popover and the highlighted element. (default: 10) +popoverOffset?: number; +// Array of buttons to show in the popover. Defaults to ["next", "previous", "close"] +// for product tours and [] for single element highlighting. +showButtons?: AllowedButtons[]; +// Array of buttons to disable. This is useful when you want to show some of the +// buttons, but disable some of them. +disableButtons?: AllowedButtons[]; + +// Whether to show the progress text in popover. (default: false) +showProgress?: boolean; +// Template for the progress text. You can use the following placeholders in the template: +// - {{current}}: The current step number +// - {{total}}: Total number of steps +progressText?: string; + +// Text to show in the buttons. `doneBtnText` +// is used on the last step of a tour. +nextBtnText?: string; +prevBtnText?: string; +doneBtnText?: string; + +// Called after the popover is rendered. +// PopoverDOM is an object with references to +// the popover DOM elements such as buttons +// title, descriptions, body, container etc. +onPopoverRender?: (popover: PopoverDOM, options: { config: Config; state: State, driver: Driver }) => void; + +// Hooks to run before and after highlighting +// each step. Each hook receives the following +// parameters: +// - element: The target DOM element of the step +// - step: The step object configured for the step +// - options.config: The current configuration options +// - options.state: The current state of the driver +// - options.driver: Current driver object +onHighlightStarted?: (element?: Element, step: DriveStep, options: { config: Config; state: State, driver: Driver }) => void; +onHighlighted?: (element?: Element, step: DriveStep, options: { config: Config; state: State, driver: Driver }) => void; +onDeselected?: (element?: Element, step: DriveStep, options: { config: Config; state: State, driver: Driver }) => void; + +// Hooks to run before and after the driver +// is destroyed. Each hook receives +// the following parameters: +// - element: Currently active element +// - step: The step object configured for the currently active +// - options.config: The current configuration options +// - options.state: The current state of the driver +// - options.driver: Current driver object +onDestroyStarted?: (element?: Element, step: DriveStep, options: { config: Config; state: State, driver: Driver }) => void; +onDestroyed?: (element?: Element, step: DriveStep, options: { config: Config; state: State, driver: Driver }) => void; + +// Hooks to run on button clicks. Each hook receives +// the following parameters: +// - element: The current DOM element of the step +// - step: The step object configured for the step +// - options.config: The current configuration options +// - options.state: The current state of the driver +// - options.driver: Current driver object +onNextClick?: (element?: Element, step: DriveStep, options: { config: Config; state: State, driver: Driver }) => void; +onPrevClick?: (element?: Element, step: DriveStep, options: { config: Config; state: State, driver: Driver }) => void; +onCloseClick?: (element?: Element, step: DriveStep, options: { config: Config; state: State, driver: Driver }) => void; +}; +Note: By overriding onNextClick, and onPrevClick hooks you control the navigation of the driver. This means that user won’t be able to navigate using the buttons and you will have to either call driverObj.moveNext() or driverObj.movePrevious() to navigate to the next/previous step. + +You can use this to implement custom logic for navigating between steps. This is also useful when you are dealing with dynamic content and want to highlight the next/previous element based on some logic. + +onNextClick and onPrevClick hooks can be configured at the step level as well. When configured at the driver level, you control the navigation for all the steps. When configured at the step level, you control the navigation for that particular step only. + +Popover Configuration +The popover is the main UI element of Driver.js. It’s the element that highlights the target element, and shows the step content. You can configure the popover globally, or per step. Given below are some of the available configuration options. + +type Popover = { +// Title and descriptions shown in the popover. +// You can use HTML in these. Also, you can +// omit one of these to show only the other. +title?: string; +description?: string; + +// The position and alignment of the popover +// relative to the target element. +side?: "top" | "right" | "bottom" | "left"; +align?: "start" | "center" | "end"; + +// Array of buttons to show in the popover. +// When highlighting a single element, there +// are no buttons by default. When showing +// a tour, the default buttons are "next", +// "previous" and "close". +showButtons?: ("next" | "previous" | "close")[]; +// An array of buttons to disable. This is +// useful when you want to show some of the +// buttons, but disable some of them. +disableButtons?: ("next" | "previous" | "close")[]; + +// Text to show in the buttons. `doneBtnText` +// is used on the last step of a tour. +nextBtnText?: string; +prevBtnText?: string; +doneBtnText?: string; + +// Whether to show the progress text in popover. +showProgress?: boolean; +// Template for the progress text. You can use +// the following placeholders in the template: +// - {{current}}: The current step number +// - {{total}}: Total number of steps +// Defaults to following if `showProgress` is true: +// - "{{current}} of {{total}}" +progressText?: string; + +// Custom class to add to the popover element. +// This can be used to style the popover. +popoverClass?: string; + +// Hook to run after the popover is rendered. +// You can modify the popover element here. +// Parameter is an object with references to +// the popover DOM elements such as buttons +// title, descriptions, body, etc. +onPopoverRender?: (popover: PopoverDOM, options: { config: Config; state: State, driver: Driver }) => void; + +// Callbacks for button clicks. You can use +// these to add custom behavior to the buttons. +// Each callback receives the following parameters: +// - element: The current DOM element of the step +// - step: The step object configured for the step +// - options.config: The current configuration options +// - options.state: The current state of the driver +// - options.driver: Current driver object +onNextClick?: (element?: Element, step: DriveStep, options: { config: Config; state: State, driver: Driver }) => void +onPrevClick?: (element?: Element, step: DriveStep, options: { config: Config; state: State, driver: Driver }) => void +onCloseClick?: (element?: Element, step: DriveStep, options: { config: Config; state: State, driver: Driver }) => void +} +Drive Step Configuration +Drive step is the configuration object passed to the highlight method or the steps array of the drive method. You can configure the popover and the target element for each step. Given below are some of the available configuration options. + +type DriveStep = { +// The target element to highlight. +// This can be a DOM element, or a CSS selector. +// If this is a selector, the first matching +// element will be highlighted. +element: Element | string; + +// The popover configuration for this step. +// Look at the Popover Configuration section +popover?: Popover; + +// Whether to disable interaction with the highlighted element. (default: false) +disableActiveInteraction?: boolean; + +// Callback when the current step is deselected, +// about to be highlighted or highlighted. +// Each callback receives the following parameters: +// - element: The current DOM element of the step +// - step: The step object configured for the step +// - options.config: The current configuration options +// - options.state: The current state of the driver +// - options.driver: Current driver object +onDeselected?: (element?: Element, step: DriveStep, options: { config: Config; state: State, driver: Driver }) => void; +onHighlightStarted?: (element?: Element, step: DriveStep, options: { config: Config; state: State, driver: Driver }) => void; +onHighlighted?: (element?: Element, step: DriveStep, options: { config: Config; state: State, driver: Driver }) => void; +} +State +You can access the current state of the driver by calling the getState method. It’s also passed to the hooks and callbacks. + +type State = { +// Whether the driver is currently active or not +isInitialized?: boolean; + +// Index of the currently active step if using +// as a product tour and have configured the +// steps array. +activeIndex?: number; +// DOM element of the currently active step +activeElement?: Element; +// Step object of the currently active step +activeStep?: DriveStep; + +// DOM element that was previously active +previousElement?: Element; +// Step object of the previously active step +previousStep?: DriveStep; + +// DOM elements for the popover i.e. including +// container, title, description, buttons etc. +popover?: PopoverDOM; +} + +Here is the list of methods provided by driver when you initialize it. + +Note: We have omitted the configuration options for brevity. Please look at the configuration section for the options. Links are provided in the description below. + +import { driver } from "driver.js"; +import "driver.js/dist/driver.css"; + +// Look at the configuration section for the options +// https://driverjs.com/docs/configuration#driver-configuration +const driverObj = driver({ /_ ... _/ }); + +// -------------------------------------------------- +// driverObj is an object with the following methods +// -------------------------------------------------- + +// Start the tour using `steps` given in the configuration +driverObj.drive(); // Starts at step 0 +driverObj.drive(4); // Starts at step 4 + +driverObj.moveNext(); // Move to the next step +driverObj.movePrevious(); // Move to the previous step +driverObj.moveTo(4); // Move to the step 4 +driverObj.hasNextStep(); // Is there a next step +driverObj.hasPreviousStep() // Is there a previous step + +driverObj.isFirstStep(); // Is the current step the first step +driverObj.isLastStep(); // Is the current step the last step + +driverObj.getActiveIndex(); // Gets the active step index + +driverObj.getActiveStep(); // Gets the active step configuration +driverObj.getPreviousStep(); // Gets the previous step configuration +driverObj.getActiveElement(); // Gets the active HTML element +driverObj.getPreviousElement(); // Gets the previous HTML element + +// Is the tour or highlight currently active +driverObj.isActive(); + +// Recalculate and redraw the highlight +driverObj.refresh(); + +// Look at the configuration section for configuration options +// https://driverjs.com/docs/configuration#driver-configuration +driverObj.getConfig(); +driverObj.setConfig({ /_ ... _/ }); + +driverObj.setSteps([ /* ... */ ]); // Set the steps + +// Look at the state section of configuration for format of the state +// https://driverjs.com/docs/configuration#state +driverObj.getState(); + +// Look at the DriveStep section of configuration for format of the step +// https://driverjs.com/docs/configuration/#drive-step-configuration +driverObj.highlight({ /_ ... _/ }); // Highlight an element + +driverObj.destroy(); // Destroy the tour + +Theming +You can customize the look and feel of the driver by adding custom class to popover or applying CSS to different classes used by driver.js. + +Styling Popover +You can set the popoverClass option globally in the driver configuration or at the step level to apply custom class to the popover and then use CSS to apply styles. + +const driverObj = driver({ +popoverClass: 'my-custom-popover-class' +}); + +// or you can also have different classes for different steps +const driverObj2 = driver({ +steps: [ +{ +element: '#some-element', +popover: { +title: 'Title', +description: 'Description', +popoverClass: 'my-custom-popover-class' +} +} +], +}) +Here is the list of classes applied to the popover which you can use in conjunction with popoverClass option to apply custom styles on the popover. + +/_ Class assigned to popover wrapper _/ +.driver-popover {} + +/_ Arrow pointing towards the highlighted element _/ +.driver-popover-arrow {} + +/_ Title and description _/ +.driver-popover-title {} +.driver-popover-description {} + +/_ Close button displayed on the top right corner _/ +.driver-popover-close-btn {} + +/_ Footer of the popover displaying progress and navigation buttons _/ +.driver-popover-footer {} +.driver-popover-progress-text {} +.driver-popover-prev-btn {} +.driver-popover-next-btn {} +Visit the example page for an example that modifies the popover styles. + +Modifying Popover DOM +Alternatively, you can also use the onPopoverRender hook to modify the popover DOM before it is displayed. The hook is called with the popover DOM as the first argument. + +type PopoverDOM = { +wrapper: HTMLElement; +arrow: HTMLElement; +title: HTMLElement; +description: HTMLElement; +footer: HTMLElement; +progress: HTMLElement; +previousButton: HTMLElement; +nextButton: HTMLElement; +closeButton: HTMLElement; +footerButtons: HTMLElement; +}; + +onPopoverRender?: (popover: PopoverDOM, opts: { config: Config; state: State }) => void; +Styling Page +Following classes are applied to the page when the driver is active. + +/_ Applied to the `body` when the driver: _/ +.driver-active {} /_ is active _/ +.driver-fade {} /_ is animated _/ +.driver-simple {} /_ is not animated _/ +Following classes are applied to the overlay i.e. the lightbox displayed over the page. + +.driver-overlay {} +Styling Highlighted Element +Whenever an element is highlighted, the following classes are applied to it. + +.driver-active-element {} + +Animated Tour +The following example shows how to create a simple tour with a few steps. Click the button below the code sample to see the tour in action. +Basic Animated Tour + +import { driver } from "driver.js"; +import "driver.js/dist/driver.css"; + +const driverObj = driver({ +showProgress: true, +steps: [ +{ element: '#tour-example', popover: { title: 'Animated Tour Example', description: 'Here is the code example showing animated tour. Let\'s walk you through it.', side: "left", align: 'start' }}, +{ element: 'code .line:nth-child(1)', popover: { title: 'Import the Library', description: 'It works the same in vanilla JavaScript as well as frameworks.', side: "bottom", align: 'start' }}, +{ element: 'code .line:nth-child(2)', popover: { title: 'Importing CSS', description: 'Import the CSS which gives you the default styling for popover and overlay.', side: "bottom", align: 'start' }}, +{ element: 'code .line:nth-child(4) span:nth-child(7)', popover: { title: 'Create Driver', description: 'Simply call the driver function to create a driver.js instance', side: "left", align: 'start' }}, +{ element: 'code .line:nth-child(18)', popover: { title: 'Start Tour', description: 'Call the drive method to start the tour and your tour will be started.', side: "top", align: 'start' }}, +{ element: 'a[href="/docs/configuration"]', popover: { title: 'More Configuration', description: 'Look at this page for all the configuration options you can pass.', side: "right", align: 'start' }}, +{ popover: { title: 'Happy Coding', description: 'And that is all, go ahead and start adding tours to your applications.' } } +] +}); + +driverObj.drive(); + +Styling Popover +You can either use the default class names and override the styles or you can pass a custom class name to the popoverClass option either globally or per step. + +Alternatively, if want to modify the Popover DOM, you can use the onPopoverRender callback to get the popover DOM element and do whatever you want with it before popover is rendered. + +We have added a few examples below but have a look at the theming section for detailed guide including class names to target etc. + +Using CSS + +import { driver } from "driver.js"; +import "driver.js/dist/driver.css"; + +const driverObj = driver({ +popoverClass: 'driverjs-theme' +}); + +driverObj.highlight({ +element: '#demo-theme', +popover: { +title: 'Style However You Want', +description: 'You can use the default class names and override the styles or you can pass a custom class name to the popoverClass option either globally or per step.' +} +}); +Driver.js Website Theme +Here is the CSS used for the above example: + +.driver-popover.driverjs-theme { +background-color: #fde047; +color: #000; +} + +.driver-popover.driverjs-theme .driver-popover-title { +font-size: 20px; +} + +.driver-popover.driverjs-theme .driver-popover-title, +.driver-popover.driverjs-theme .driver-popover-description, +.driver-popover.driverjs-theme .driver-popover-progress-text { +color: #000; +} + +.driver-popover.driverjs-theme button { +flex: 1; +text-align: center; +background-color: #000; +color: #ffffff; +border: 2px solid #000; +text-shadow: none; +font-size: 14px; +padding: 5px 8px; +border-radius: 6px; +} + +.driver-popover.driverjs-theme button:hover { +background-color: #000; +color: #ffffff; +} + +.driver-popover.driverjs-theme .driver-popover-navigation-btns { +justify-content: space-between; +gap: 3px; +} + +.driver-popover.driverjs-theme .driver-popover-close-btn { +color: #9b9b9b; +} + +.driver-popover.driverjs-theme .driver-popover-close-btn:hover { +color: #000; +} + +.driver-popover.driverjs-theme .driver-popover-arrow-side-left.driver-popover-arrow { +border-left-color: #fde047; +} + +.driver-popover.driverjs-theme .driver-popover-arrow-side-right.driver-popover-arrow { +border-right-color: #fde047; +} + +.driver-popover.driverjs-theme .driver-popover-arrow-side-top.driver-popover-arrow { +border-top-color: #fde047; +} + +.driver-popover.driverjs-theme .driver-popover-arrow-side-bottom.driver-popover-arrow { +border-bottom-color: #fde047; +} + +Using Hook to Modify + +import { driver } from "driver.js"; +import "driver.js/dist/driver.css"; + +const driverObj = driver({ +// Get full control over the popover rendering. +// Here we are adding a custom button that takes +// the user to the first step. +onPopoverRender: (popover, { config, state }) => { +const firstButton = document.createElement("button"); +firstButton.innerText = "Go to First"; +popover.footerButtons.appendChild(firstButton); + + firstButton.addEventListener("click", () => { + driverObj.drive(0); + }); + +}, +steps: [ +// .. +] +}); + +driverObj.drive(); + +Tour Progress +You can use showProgress option to show the progress of the tour. It is shown in the bottom left corner of the screen. There is also progressText option which can be used to customize the text shown for the progress. + +Please note that showProgress is false by default. Also the default text for progressText is {{current}} of {{total}}. You can use {{current}} and {{total}} in your progressText template to show the current and total steps. + +import { driver } from "driver.js"; +import "driver.js/dist/driver.css"; + +const driverObj = driver({ +showProgress: true, +showButtons: ['next', 'previous'], +steps: [ +{ element: '#tour-example', popover: { title: 'Animated Tour Example', description: 'Here is the code example showing animated tour. Let\'s walk you through it.', side: "left", align: 'start' }}, +{ element: 'code .line:nth-child(1)', popover: { title: 'Import the Library', description: 'It works the same in vanilla JavaScript as well as frameworks.', side: "bottom", align: 'start' }}, +{ element: 'code .line:nth-child(2)', popover: { title: 'Importing CSS', description: 'Import the CSS which gives you the default styling for popover and overlay.', side: "bottom", align: 'start' }}, +{ element: 'code .line:nth-child(4) span:nth-child(7)', popover: { title: 'Create Driver', description: 'Simply call the driver function to create a driver.js instance', side: "left", align: 'start' }}, +{ element: 'code .line:nth-child(16)', popover: { title: 'Start Tour', description: 'Call the drive method to start the tour and your tour will be started.', side: "top", align: 'start' }}, +] +}); + +driverObj.drive(); + +Async Tour +You can also have async steps in your tour. This is useful when you want to load some data from the server and then show the tour. + +Asynchronous Tour + +import { driver } from "driver.js"; +import "driver.js/dist/driver.css"; + +const driverObj = driver({ +showProgress: true, +steps: [ +{ +popover: { +title: 'First Step', +description: 'This is the first step. Next element will be loaded dynamically.' +// By passing onNextClick, you can override the default behavior of the next button. +// This will prevent the driver from moving to the next step automatically. +// You can then manually call driverObj.moveNext() to move to the next step. +onNextClick: () => { +// .. load element dynamically +// .. and then call +driverObj.moveNext(); +}, +}, +}, +{ +element: '.dynamic-el', +popover: { +title: 'Async Element', +description: 'This element is loaded dynamically.' +}, +// onDeselected is called when the element is deselected. +// Here we are simply removing the element from the DOM. +onDeselected: () => { +// .. remove element +document.querySelector(".dynamic-el")?.remove(); +} +}, +{ popover: { title: 'Last Step', description: 'This is the last step.' } } +] + +}); + +driverObj.drive(); +Show me an Example +Note: By overriding onNextClick, and onPrevClick hooks you control the navigation of the driver. This means that user won’t be able to navigate using the buttons and you will have to either call driverObj.moveNext() or driverObj.movePrevious() to navigate to the next/previous step. + +You can use this to implement custom logic for navigating between steps. This is also useful when you are dealing with dynamic content and want to highlight the next/previous element based on some logic. + +onNextClick and onPrevClick hooks can be configured at driver level as well as step level. When configured at the driver level, you control the navigation for all the steps. When configured at the step level, you control the navigation for that particular step only. + +Confirm on Exit +You can use the onDestroyStarted hook to add a confirmation dialog or some other logic when the user tries to exit the tour. In the example below, upon exit we check if there are any tour steps left and ask for confirmation before we exit. + +Confirm on Exit + +import { driver } from "driver.js"; +import "driver.js/dist/driver.css"; + +const driverObj = driver({ +showProgress: true, +steps: [ +{ element: '#confirm-destroy-example', popover: { title: 'Animated Tour Example', description: 'Here is the code example showing animated tour. Let\'s walk you through it.', side: "left", align: 'start' }}, +{ element: 'code .line:nth-child(1)', popover: { title: 'Import the Library', description: 'It works the same in vanilla JavaScript as well as frameworks.', side: "bottom", align: 'start' }}, +{ element: 'code .line:nth-child(2)', popover: { title: 'Importing CSS', description: 'Import the CSS which gives you the default styling for popover and overlay.', side: "bottom", align: 'start' }}, +{ popover: { title: 'Happy Coding', description: 'And that is all, go ahead and start adding tours to your applications.' } } +], +// onDestroyStarted is called when the user tries to exit the tour +onDestroyStarted: () => { +if (!driverObj.hasNextStep() || confirm("Are you sure?")) { +driverObj.destroy(); +} +}, +}); + +driverObj.drive(); +Show me an Example +Note: By overriding the onDestroyStarted hook, you are responsible for calling driverObj.destroy() to exit the tour. + +Prevent Tour Exit +You can also prevent the user from exiting the tour using allowClose option. This option is useful when you want to force the user to complete the tour before they can exit. + +In the example below, you won’t be able to exit the tour until you reach the last step. +Prevent Exit + +import { driver } from "driver.js"; +import "driver.js/dist/driver.css"; + +const driverObj = driver({ +showProgress: true, +allowClose: false, +steps: [ +{ element: '#prevent-exit', popover: { title: 'Animated Tour Example', description: 'Here is the code example showing animated tour. Let\'s walk you through it.', side: "left", align: 'start' }}, +{ element: 'code .line:nth-child(1)', popover: { title: 'Import the Library', description: 'It works the same in vanilla JavaScript as well as frameworks.', side: "bottom", align: 'start' }}, +{ element: 'code .line:nth-child(2)', popover: { title: 'Importing CSS', description: 'Import the CSS which gives you the default styling for popover and overlay.', side: "bottom", align: 'start' }}, +{ popover: { title: 'Happy Coding', description: 'And that is all, go ahead and start adding tours to your applications.' } } +], +}); + +driverObj.drive(); + +Styling Overlay +You can customize the overlay opacity and color using overlayOpacity and overlayColor options to change the look of the overlay. + +Note: In the examples below we have used highlight method to highlight the elements. The same configuration applies to the tour steps as well. + +Overlay Color +Here are some driver.js examples with different overlay colors. + +import { driver } from "driver.js"; +import "driver.js/dist/driver.css"; + +const driverObj = driver({ +overlayColor: 'red' +}); + +driverObj.highlight({ +popover: { +title: 'Pass any RGB Color', +description: 'Here we have set the overlay color to be red. You can pass any RGB color to overlayColor option.' +} +}); + +Popover Position +You can control the popover position using the side and align options. The side option controls the side of the element where the popover will be shown and the align option controls the alignment of the popover with the element. + +Note: Popover is intelligent enough to adjust itself to fit in the viewport. So, if you set side to left and align to start, but the popover doesn’t fit in the viewport, it will automatically adjust itself to fit in the viewport. Consider highlighting and scrolling the browser to the element below to see this in action. + +import { driver } from "driver.js"; +import "driver.js/dist/driver.css"; + +const driverObj = driver(); +driverObj.highlight({ +element: '#left-start', +popover: { +title: 'Animated Tour Example', +description: 'Here is the code example showing animated tour. Let\'s walk you through it.', +side: "left", +align: 'start' +} +}); + +Popover Buttons +You can use the showButtons option to choose which buttons to show in the popover. The default value is ['next', 'previous', 'close']. + +Note: When using the highlight method to highlight a single element, the only button shown is the close button. However, you can use the showButtons option to show other buttons as well. But the buttons won’t do anything. You will have to use the onNextClick and onPreviousClick callbacks to implement the functionality. + +import { driver } from "driver.js"; +import "driver.js/dist/driver.css"; + +const driverObj = driver({ +showButtons: [ +'next', +'previous', +'close' +], +steps: [ +{ +element: '#first-element', +popover: { +title: 'Popover Title', +description: 'Popover Description' +} +}, +{ +element: '#second-element', +popover: { +title: 'Popover Title', +description: 'Popover Description' +} +} +] +}); + +driverObj.drive(); + +Simple Highlight +Product tours is not the only usecase for Driver.js. You can use it to highlight any element on the page and show a popover with a description. This is useful for providing contextual help to the user e.g. help the user fill a form or explain a feature. + +Example below shows how to highlight an element and simply show a popover. + +Highlight Me +Here is the code for above example: + +const driverObj = driver({ +popoverClass: "driverjs-theme", +stagePadding: 4, +}); + +driverObj.highlight({ +element: "#highlight-me", +popover: { +side: "bottom", +title: "This is a title", +description: "This is a description", +} +}) +You can also use it to show a simple modal without highlighting any element. + +Show Popover +Here is the code for above example: + +const driverObj = driver(); + +driverObj.highlight({ +popover: { +description: "Yet another highlight example.", +} +}) +Focus on the input below and see how the popover is shown. + +Enter your Name +Your Education +Your Age +Your Address +Submit +Here is the code for the above example. + +const driverObj = driver({ +popoverClass: "driverjs-theme", +stagePadding: 0, +onDestroyed: () => { +document?.activeElement?.blur(); +} +}); + +const nameEl = document.getElementById("name"); +const educationEl = document.getElementById("education"); +const ageEl = document.getElementById("age"); +const addressEl = document.getElementById("address"); +const formEl = document.querySelector("form"); + +nameEl.addEventListener("focus", () => { +driverObj.highlight({ +element: nameEl, +popover: { +title: "Name", +description: "Enter your name here", +}, +}); +}); + +educationEl.addEventListener("focus", () => { +driverObj.highlight({ +element: educationEl, +popover: { +title: "Education", +description: "Enter your education here", +}, +}); +}); + +ageEl.addEventListener("focus", () => { +driverObj.highlight({ +element: ageEl, +popover: { +title: "Age", +description: "Enter your age here", +}, +}); +}); + +addressEl.addEventListener("focus", () => { +driverObj.highlight({ +element: addressEl, +popover: { +title: "Address", +description: "Enter your address here", +}, +}); +}); + +formEl.addEventListener("blur", () => { +driverObj.destroy(); +}); diff --git a/package-lock.json b/package-lock.json index 54e76f7..cebb8f8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "cmdk": "^1.0.4", "compromise": "^14.14.4", "date-fns": "^4.1.0", + "driver.js": "^1.3.5", "framer-motion": "^12.4.1", "lucide-react": "^0.475.0", "react": "^18.3.1", @@ -2787,6 +2788,11 @@ "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", "license": "MIT" }, + "node_modules/driver.js": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/driver.js/-/driver.js-1.3.5.tgz", + "integrity": "sha512-exkp49hXuujvTOZ3zYgySWRlEAa8/3nA8glYjtuZjmkTdsQITXivBsW1ytyhKQx3WkeYaovlnvVcLbtTaN86kA==" + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", diff --git a/package.json b/package.json index c790c26..4ad2c51 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "cmdk": "^1.0.4", "compromise": "^14.14.4", "date-fns": "^4.1.0", + "driver.js": "^1.3.5", "framer-motion": "^12.4.1", "lucide-react": "^0.475.0", "react": "^18.3.1", diff --git a/src/components/ui/tour/AppTour.tsx b/src/components/ui/tour/AppTour.tsx new file mode 100644 index 0000000..685fe11 --- /dev/null +++ b/src/components/ui/tour/AppTour.tsx @@ -0,0 +1,110 @@ +import React, { useState, useEffect } from 'react'; +import { driver } from 'driver.js'; +import 'driver.js/dist/driver.css'; + +// Define a type for our tour steps +interface TourStep { + element: string; + popover: { + title: string; + description: string; + position?: string; + }; +} + +// Create a predefined tour +const tourSteps: TourStep[] = [ + { + element: '#statementList', + popover: { + title: 'Welcome to Beacons!', + description: 'This is where all your statements will appear.', + position: 'bottom' + } + }, + { + element: '.category-section', + popover: { + title: 'Categories', + description: 'Statements are organized by categories to help you navigate them easily.', + position: 'bottom' + } + }, + { + element: '.question-card', + popover: { + title: 'Questions', + description: 'These are questions that will help you create meaningful statements.', + position: 'top' + } + }, + { + element: '.add-custom-button', + popover: { + title: 'Add Custom Statements', + description: 'You can also create your own custom statements from scratch!', + position: 'top' + } + } +]; + +// Create a custom hook to manage the tour +export const useTour = () => { + const [driverObj, setDriverObj] = useState(null); + const [hasSeenTour, setHasSeenTour] = useState(false); + + useEffect(() => { + // Check if user has seen the tour before + const tourSeen = localStorage.getItem('tour_completed'); + if (tourSeen) { + setHasSeenTour(true); + } + + // Initialize driver + const driverInstance = driver({ + showProgress: true, + steps: tourSteps, + nextBtnText: 'Next', + prevBtnText: 'Previous', + doneBtnText: 'Done', + onDestroyed: () => { + // Mark tour as completed when finished + localStorage.setItem('tour_completed', 'true'); + setHasSeenTour(true); + } + }); + + setDriverObj(driverInstance); + + return () => { + // Cleanup when component unmounts + if (driverInstance) { + driverInstance.destroy(); + } + }; + }, []); + + // Start the tour + const startTour = () => { + if (driverObj) { + driverObj.drive(); + } + }; + + return { startTour, hasSeenTour }; +}; + +// Tour button component +export const TourButton: React.FC = () => { + const { startTour } = useTour(); + + return ( + + ); +}; \ No newline at end of file diff --git a/src/data/setQuestions.json b/src/data/setQuestions.json index 1c37fee..1e91f6b 100644 --- a/src/data/setQuestions.json +++ b/src/data/setQuestions.json @@ -405,6 +405,35 @@ "presetAnswer": null } } + }, + { + "id": "q15", + "category": "Example", + "mainQuestion": "How do you feel abour having pets on your working place?", + "steps": { + "subject": { + "question": "Who is this statement about?", + "preset": true, + "presetAnswer": "username", + "allowDescriptors": true + }, + "verb": { + "question": "Select the action verb for your statement.", + "preset": false, + "presetAnswer": "Introduce", + "allowedVerbs": ["Introduce", "Share"] + }, + "object": { + "question": "Provide a brief personal and professional introduction including key details.", + "preset": false, + "presetAnswer": null + }, + "privacy": { + "question": "Do you want to share this introduction with your team?", + "preset": false, + "presetAnswer": null + } + } } ] } diff --git a/src/data/statementsCategories.json b/src/data/statementsCategories.json index ce508be..1b90d83 100644 --- a/src/data/statementsCategories.json +++ b/src/data/statementsCategories.json @@ -1,5 +1,9 @@ { "categories": [ + { + "id": "Example", + "name": "Category example" + }, { "id": "Wellbeing", "name": "Wellness at work" diff --git a/src/features/email/api/gratitudeApi.ts b/src/features/email/api/gratitudeApi.ts index 7ed6e33..c3dad31 100644 --- a/src/features/email/api/gratitudeApi.ts +++ b/src/features/email/api/gratitudeApi.ts @@ -45,9 +45,11 @@ const mockMarkGratitudeSent = async (statementId: string, actionId: string, mess byDate: '', action: `Mock action for statement ${statementId}`, completed: true, - gratitudeSent: true, - gratitudeMessage: message, - gratitudeSentDate: new Date().toISOString() + gratitude: { + sent: true, + message: message, + sentDate: new Date().toISOString() + } }; }; diff --git a/src/features/statements/components/ActionLine.tsx b/src/features/statements/components/ActionLine.tsx index a57c5b3..f58e857 100644 --- a/src/features/statements/components/ActionLine.tsx +++ b/src/features/statements/components/ActionLine.tsx @@ -143,7 +143,7 @@ const ActionLine: React.FC = ({ )} {/* Show gratitude sent icon with tooltip */} - {action.gratitudeSent && ( + {action.gratitude?.sent && (
@@ -158,14 +158,14 @@ const ActionLine: React.FC = ({

Gratitude Sent

- {action.gratitudeSentDate && ( + {action.gratitude?.sentDate && (

- {new Date(action.gratitudeSentDate).toLocaleDateString()} + {new Date(action.gratitude.sentDate).toLocaleDateString()}

)} - {action.gratitudeMessage && ( + {action.gratitude?.message && (

- "{action.gratitudeMessage}" + "{action.gratitude.message}"

)}
@@ -217,7 +217,7 @@ const ActionLine: React.FC = ({ {/* Only hide gratitude option for actions that already had gratitude sent */} - {!action.gratitudeSent && ( + {!action.gratitude?.sent && ( <> {/* Determine if manager email is set */} diff --git a/src/features/statements/components/QuestionCard.tsx b/src/features/statements/components/QuestionCard.tsx index 1099f6f..d9b87fb 100644 --- a/src/features/statements/components/QuestionCard.tsx +++ b/src/features/statements/components/QuestionCard.tsx @@ -24,7 +24,7 @@ const QuestionCard: React.FC = ({ }; return ( -
+
{/* Question Card */}
{} : () => onSelect(presetQuestion)} diff --git a/src/features/statements/components/StatementList.tsx b/src/features/statements/components/StatementList.tsx index f6b4ab3..ef206eb 100644 --- a/src/features/statements/components/StatementList.tsx +++ b/src/features/statements/components/StatementList.tsx @@ -369,7 +369,7 @@ const StatementList: React.FC = ({ return ( -
+
{/* Folder Tab Design */}
= ({ return ( <> -
+
{/* Regular categories */} {definedCategories.map((cat) => renderCategorySection(cat.id, cat.name) @@ -561,7 +561,7 @@ const StatementList: React.FC = ({ +
) : (
diff --git a/src/layouts/components/MainPage.tsx b/src/layouts/components/MainPage.tsx index 6faa9a4..aae2fff 100644 --- a/src/layouts/components/MainPage.tsx +++ b/src/layouts/components/MainPage.tsx @@ -1,6 +1,6 @@ 'use client'; -import React, { useState } from 'react'; +import React, { useState, useEffect } from 'react'; import { Tooltip, TooltipTrigger, @@ -15,6 +15,7 @@ import ShareEmailModal from '../../components/modals/ShareEmailModal'; import PrivacyModal from '../../components/modals/PrivacyModal'; import TermsModal from '../../components/modals/TermsModal'; import TestStatementButton from '../../components/debug/TestButton'; +import { useTour } from '../../components/ui/tour/AppTour'; const MainPage: React.FC = () => { const { data } = useEntries(); @@ -23,6 +24,22 @@ const MainPage: React.FC = () => { const [isShareModalOpen, setIsShareModalOpen] = useState(false); const [isPrivacyModalOpen, setIsPrivacyModalOpen] = useState(false); const [isTermsModalOpen, setIsTermsModalOpen] = useState(false); + + // Get tour functionality + const { startTour, hasSeenTour } = useTour(); + + // Auto-start tour for new users + useEffect(() => { + // Check if the user is authenticated and has not seen the tour + if (username && !hasSeenTour) { + // Wait for the UI to fully render before starting the tour + const tourTimeout = setTimeout(() => { + startTour(); + }, 1000); + + return () => clearTimeout(tourTimeout); + } + }, [username, hasSeenTour, startTour]); // Determine if email button should be disabled: const hasManagerEmail = managerEmail && managerEmail.trim().length > 0; From cad3602e389bbe8c1abce9ea163232e7bf8a389a Mon Sep 17 00:00:00 2001 From: Alexander Date: Fri, 21 Mar 2025 12:08:43 +0000 Subject: [PATCH 03/34] fix: modified z-index to make tour popover on top of header --- src/components/ui/tour/AppTour.tsx | 45 ++++++++++++------- src/components/ui/tour/tour.css | 22 +++++++++ .../statements/components/StatementItem.tsx | 8 ++-- 3 files changed, 56 insertions(+), 19 deletions(-) create mode 100644 src/components/ui/tour/tour.css diff --git a/src/components/ui/tour/AppTour.tsx b/src/components/ui/tour/AppTour.tsx index 685fe11..a6680db 100644 --- a/src/components/ui/tour/AppTour.tsx +++ b/src/components/ui/tour/AppTour.tsx @@ -1,6 +1,7 @@ import React, { useState, useEffect } from 'react'; import { driver } from 'driver.js'; import 'driver.js/dist/driver.css'; +import './tour.css'; // Import our custom tour styles // Define a type for our tour steps interface TourStep { @@ -8,7 +9,8 @@ interface TourStep { popover: { title: string; description: string; - position?: string; + side?: "top" | "right" | "bottom" | "left"; + align?: "start" | "center" | "end"; }; } @@ -19,33 +21,40 @@ const tourSteps: TourStep[] = [ popover: { title: 'Welcome to Beacons!', description: 'This is where all your statements will appear.', - position: 'bottom' - } + side: 'right', + align: 'start' + }, }, { element: '.category-section', popover: { title: 'Categories', - description: 'Statements are organized by categories to help you navigate them easily.', - position: 'bottom' - } + description: + 'Statements are organized by categories to help you navigate them easily.', + side: 'right', + align: 'start' + }, }, { element: '.question-card', popover: { title: 'Questions', - description: 'These are questions that will help you create meaningful statements.', - position: 'top' - } + description: + 'These are questions that will help you create meaningful statements.', + side: 'right', + align: 'start' + }, }, { element: '.add-custom-button', popover: { title: 'Add Custom Statements', - description: 'You can also create your own custom statements from scratch!', - position: 'top' - } - } + description: + 'You can also create your own custom statements from scratch!', + side: 'left', + align: 'start' + }, + }, ]; // Create a custom hook to manage the tour @@ -67,6 +76,10 @@ export const useTour = () => { nextBtnText: 'Next', prevBtnText: 'Previous', doneBtnText: 'Done', + stagePadding: 10, // Add stage padding around highlighted element + popoverOffset: 15, // Distance between popover and element + smoothScroll: true, // Use built-in smooth scrolling + onDestroyed: () => { // Mark tour as completed when finished localStorage.setItem('tour_completed', 'true'); @@ -101,10 +114,10 @@ export const TourButton: React.FC = () => { return ( ); -}; \ No newline at end of file +}; diff --git a/src/components/ui/tour/tour.css b/src/components/ui/tour/tour.css new file mode 100644 index 0000000..5dbfb17 --- /dev/null +++ b/src/components/ui/tour/tour.css @@ -0,0 +1,22 @@ +/* Custom styles for driver.js tour */ +.driver-popover { + max-width: 300px; +} + +/* Style the next button with app's pink color theme */ +.driver-popover-next-btn { + background-color: #ec4899; +} + +.driver-popover-next-btn:hover { + background-color: #db2777; +} + +/* Fix for z-index to ensure popover is above sticky header */ +.driver-active { + z-index: 1000; +} + +.driver-active .driver-highlighted-element { + z-index: 1001; +} \ No newline at end of file diff --git a/src/features/statements/components/StatementItem.tsx b/src/features/statements/components/StatementItem.tsx index ffe9c48..1303188 100644 --- a/src/features/statements/components/StatementItem.tsx +++ b/src/features/statements/components/StatementItem.tsx @@ -804,9 +804,11 @@ const StatementItem: React.FC = ({ const action = updatedActions[actionIndex]; const updatedAction = { ...action, - gratitudeSent: true, - gratitudeMessage: message, - gratitudeSentDate: new Date().toISOString() + gratitude: { + sent: true, + message: message, + sentDate: new Date().toISOString() + } }; // Replace the action in the array From 17f195a0ca902a38fee68b12358db80368e3c319 Mon Sep 17 00:00:00 2001 From: Alexander Date: Sun, 23 Mar 2025 12:58:45 +0000 Subject: [PATCH 04/34] feat: added driver tour --- docs/IGT DPIA Screening Tool.docx | Bin 89404 -> 0 bytes docs/IGT DPIA Screening Tool.rtf | 990 ------------------ driver.js.md | 885 ---------------- package-lock.json | 18 +- package.json | 3 +- src/components/ui/tour/AppTour.tsx | 123 --- src/components/ui/tour/TourButton.tsx | 20 + src/components/ui/tour/tour.css | 206 +++- src/components/ui/tour/useTour.ts | 508 +++++++++ src/data/descriptors.json | 11 +- src/data/setQuestions.json | 2 +- .../statements/components/StatementList.tsx | 2 +- src/layouts/components/Header.tsx | 4 +- src/layouts/components/MainPage.tsx | 4 +- 14 files changed, 750 insertions(+), 2026 deletions(-) delete mode 100644 docs/IGT DPIA Screening Tool.docx delete mode 100644 docs/IGT DPIA Screening Tool.rtf delete mode 100644 driver.js.md delete mode 100644 src/components/ui/tour/AppTour.tsx create mode 100644 src/components/ui/tour/TourButton.tsx create mode 100644 src/components/ui/tour/useTour.ts diff --git a/docs/IGT DPIA Screening Tool.docx b/docs/IGT DPIA Screening Tool.docx deleted file mode 100644 index c76ebe97cb82651de11242355a37bb3afbb071da..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 89404 zcmeEtV{k8Bx92}jPVD5www;{Vwr$(CZQHheV%xTp6FZsnytm$4cV_Ol`FLkl?e5;a zdRO&cy|j0)pPVEJC<*`^00{s92msQ9ie?i)007|+000>P39Kn-YvW{Ws@r~kncm`stj#9}}Qxe5LX6<*m*PE1jVSm9_otA}L{)-g6; zMq5IkUk(5E7VUgen2%lw?h!5AS>=;j?~Kq+QB?6u)G=?ZTAk^v zO^mG)BhNw_<|CIpA1_4`!-m~uY5_-|Vx~|PV?w{_FWEI`c>SzL5vo8%ZZjfmC#aa*=caIf ze4?4e*nPF+#HpvIIY=gVOW*-XpP?u64VpRTP&6Az^P;n zp3vgd=1VN3Qnl~TH7b4A_YTZdr;}x;pBUZQ2Yh2AZje|TOWd^V>>FGugb-2`P@N;L zP+RCw>>T+QBOYjZx9Ev|l!eoBAj0mV7>wr$e-J7p*zLpD3p~H2mkXLt3T0>6@MuMBS0nD`=b2EtuQE-xpu^J?AH;){FC zooR3C`>*~!;Og22&jM}@bsYed$2iow~@2V4~`|6;?SoL0z|IS>>TzztqX#fDX6d(ZN zUm3aDIvCLz*%~@q|Bajf1k6kB4Vwe@lpE+vUdXjni@c8CT;+O{VB$$7fi_m?9Tlw% zvd}{+2GVOOstE_CbI#m8ENCQ^x84vG{{W31yjIe-0f#Z~Zqeeh;>x=!d4 zYrGv{dI8Gf7;8&?T)4d&Y24`*;f-J_B|os|#WguNJXrHq$nx&^nE8I0xGczhEGab7 z;`}k#)|Mvifj0wg%+b!yOXm@2go2JIVoo;`aar;)J0WtiHYL3-Ic=)G%_|B3zPC;= z?Jgo;ybo~mLPs}cY1@9@%6q(bE3sJyjJew)oEYDELxJ#MKytgz3{m1gK=N4xLDmHP zh6f*-W>KN?8!ZG-RH@+4jqsmiV0&T{H~k3bg+tN_CdP#@56%{;gvpUksJG}EyOYUC`Iqrb_dh>K{CN*&w1@m-G~KN(yud5cV^2>O}__|5R_}O1iZe@ z5FKTGCd-gf0lUlVPodZXKJBLb>m*PfDxfO$(v13;UI_M}t2N06mTXJD>e#MAea>#4 zM1L=ab5^7TD38}00W#>s?G)bI6GUZ>?maNnKXYcTy@!qVfM1I`s3gFxv5+uLkQg=$ zH4wIOTOijwZEPS+1RZcA8qJZl$s;ZIong8#hNSVvY^Exi%)KzZ0HU`9yrU#Av=H~o z7)BYbfp28e@2e6+-8Zdv%+*o*UiS0Iq^$R$G)5)dG<6)R zYx?c^&9UI;=n(Pr0Tqd0k^m{e^US2Ov4Es^vbzxdxqcAqePo~As!3D<3j<0Suh($$kqOr%N`=P?g+&cwIe>K(Pur|cv2GKeK%t^AU%?NVug{GNorCUVKW z1DJu2X*wI(@-sqIR|Lqhmp|B^mh`?!&r5|H01ELnz@Dkvxb{u+V7(H`yiRSl;hVdr z)NWRn{+mABajQ5Lwy~?EHGflXrWPS8tu#dfnk2p*&8d8b3acbL7aOFPwg)Lct zFAu*4*8u`G;}QoSx4$g1+dDkS^4J3J(prT320h(3at<6-xaMHHRnwSq*qiBaBs}PC zjiZmKpg=i6!Pr-|ViI$tut-om{mhG;tNI8CR{~ck2Uj={0^unxGO!p`Z2}|=`Fu-lxegZHHpEhfOahZh1sZ+Y4k^$E1d0> zKwMaQpp;&CP!zQQ~k z`2l~z=n<_QhpQv2wd5LyCf&Tx#+ZDhAZq;Y5m6VH+9cY~t&(LFlc#GN?Gt*bd`@S9 zDiyFPLs+KoH?nGqk^Trs7@LhW%jZ2Ptu8zFnG!I&mx3pI0n#p=;NZka^uE@V=0|zM zPaq_&4$t>?vOWap$_`aOExH1m`}0l65NMr!))S%;BEw1^z^Ov?Xq_ zt8Ui@Gez8FUktX-b?w3=cnx9D4%^``V91GWjoy)P1zYITxZ0Tm0UyfSKq0{Sz zc;M`kWQvUcLJ#U`CJZQ&{OBvZ!t_iGsCxRDlyvB zMc&3i|DNcH_!R+1)uZbiED#H#&t%)$@rF#o1!_WDmc12Bgm26Tay!3->R5mAL*Zgy zm{P^z2*%?|=`!F)XF1hQaZT@^u+Rnnr(Gc}}&nUB}3lE_9sdTR9Je z+G#7K^|=Q#!9=~YcChQ85Et#*C;-0KSHV+X&50Aj*L&F9Ozm5Q3RN8c{biH;L0lW5BPWes$` z9s+t4L^5&7+@Smj`r^?8-x5%BDfi>0nk4Z1_;w(P&bD!(9&&Dew8%FecC?|?rw>N! zG`2Bs6zu0wcY4>}bflT}e85Cs8%m84JM`Sjxmf}D4}vySY_bF}YMwym{2{0%V6;JV zgeWXhnyx%(3)3;z7~e=UHYI{5oV4?>;j_V(b=igVFi?Iq#XKDx75rPD7@m`0g7Mmr ze(L0e5-IJ;aI9sjn#Bq?6GlRTY=onSjFIDk${5Wz(@^10 zM%sv~)L~BdUngLKm+ngKksKU9bKPN+x8bRpIS$s`omge5%(zV#z|d=-)}aJP#W4O9V&< zybb5)tsYYsx^5l+Gwtx2C0J%l#5g>r>5(TH3&r#=d&bYXhPy`ob*xFn#34d9b*`8e zs>U(ZGJP{wrfJ(L0@Umwvsk8Cr5AzEwcyTYyy2I=XRlk6Jc6RW=eZP;0bYM?LjMVX z{!=)hM=mA2*)gSdlTQ*`e1+XqT7YlRTYTLZ^dj)}WMwf{SuzVi2(@Dj>-}vZZl3|+ z{q6^b#6Jqs*zsyYf`^An)tTxg3vD=^kuqI{PFZ+9m^k*QXM>*A!HpChnQ9`FY`_!N zl5=brFRQn_vcuw?R5?U(#}W18?Z)@3vJT|BX5TT9ctgwUIv9U<31+!))AH86LKZpP z)7T@G-FQSFKic7bAxrBh6^6_rhUapKZEqAB^T}0W{bq+PJTJ6t&GwZ{7FXyugwp9z z4>&;{9TCBY%cR`>ZcUJxE3Y@1G$Sf^I8A#MFAxEFQhVXv1V z16e!l)g|HVbwA`@dF@4%50!}xgIfw}G%8)Xv{W(bMqw1h( zL_ga4LlgvovYoOgv-te-x@B}bN=UU9j&x|G;Ozy}Wy^|b-NvM{iGy`2M;cdi6FLa} zNvhvGY{w=@1QahChc^Y;Rf`33>EsoQ@%t{Pgpq$ow9TPFsdi?wsK0_$}9G8n!vj7a0Om735NEVcXB zX%*|$)X#JYmhto(PLi!owur|_GuA=0Ww)2LIT3QHLW~181aw%8*z{`9P&>R%u<7u zE%~Md-c=@`#=Kc%pVItKpk*@@=1d}3i5Fq97-Dfl@c66})~O6_TC+aV3t zCGmmVvGqpMT31kVnF*eVi|CE~-@e_0P?$=-643?{5IUJ1*nXTX(=e7&wl^lzFrt>c zd#`H@tnHm^mN1_AI&7a^8Qx^K?WnS$-LQ462o=OdejfO?FK@9^Z_(aFGGcP5&Wn%h ztndrIBmD}{3R;y3HuFUPHBBg!zG^HP!IGD1iQ^1p5=KO22wvZp)~9NLLqV7Wab+R8 z`5jBQ5ZjP<`M7WO-$6wT<3wRVk%| zkUCaf&ZKMlp5Q<)<7w{xtwVQI(rV61qm-+kvDxJcF8+%e0)iDb>jy#jCt3uNJx>2` zXApzzlG_ex5MW&N8ts@U^U-v#YJcR|Ffwh=TLb}Y1#>tzPbS#RPd4xmgtW%*Tmm}> z%%q;C!fl+O2gH*6o`=aqSM~*j!!z8E+#0# z<;}<=GiBVqYDen!k0Umx`*Lzz@UhWw^)JnxZ@nDt2{TB$(I9UN7JE$9^Iq>wN4CQ9 z$Tr7!pi(~!c(?DUmJ`%;517B&WHs;(Uoi($wD9t zE5n-qo1D+=6La3PWR>{_wk5m8PHEq%upDsVxYvMXnl+ilcm_LsZr(bPO-xy#)Z$n~ z%E>A^#1Z&nb6?gK7Q6@t7t2-QR)E2B4Vy3}Dk4PdfORAMi{4RZjP972_nuOdh+TL- z^SGRcyMWU|L6%=%<%x%VdCk9s1D8goSZP7w9Q~lgP_*B>PD1yzUF9QIJ9jPpgQ(m| zhVW9EtG9)t%NpmCu*sE$v0C3KQ*G8(PKG*XjOP!>b{N7L9eoH!6eJC@IhT73p}P9w zy!J?XJsYqD^u;cQ_|>dABNq0OlL62*>d$Th#`) z{W>*W%0h>l-BUeb+gVlJi?Rm9Wvdbf{ucmQV=aN{^+O_~Oz6;iwR`lzyuA#c=`S2@ zvUZ-G)9%D=_QWc3g(DfO-;08MU>CaBUv5R)xiH?#R=wepsD$uIb`K3>gu^+Bcm2ou z`l*@61Oh>Jo=hw8lOV|YyOK@CErrqV4BgRzL9uZ{^y}qCM%|B=tyw-V&(`X$ih9QSpq^$v##4y>M|Uy6ZE<^Bha z@rKAqBmhZE6f2TBKZ7E5vND<#E4?o`Ox2T5me%MPA%0f3JeJT8zZbYoBBbO$g$|pJ zmFP;W)C5g@#Rj(|AMC)0F0&xbifzIqOeN|lolnOGceHsfQLShwL{3CHMKzU?Li48$ zBJk(|%Xo<1TFauMUS%TlCs*B1A{f<|<{CyR3x{S}L+YP?oys}z{|edey)o zxw=%3*htTnVgZ_LF@9K$Z4P=h*Mp>9;qjPEdJ!*#L z;NU1WPNAGe1nyBc$6PFTwu9)7{_#~17H zh}jY7D|o%04R;S}RN?fokf_eoZwl4KclCH~Pf&!T#sG(|fuLUXu>m!4Cp#)AF=BNi z?3WdDl4oeOnPJo0x8xX0}+!`0zqTH6zC4l!sloac}Q zp91)gSq}BjbFifnU5_Hvpffk`TKK^adv$mDNm%bkjUv1(D-ASD3Hlh8rXAm9G3zoQ zceH@eURUEL)kP~5g>a(x%}lW%sowlAj-V?gEjSDy456x)8e7^C@YF#4W0Cmp-^g20 zZ8@r2>q%h0^Y3^zhU)$@5w+nq^G903-#|d_XTkIbtJjlM4;`9LZtMJC_rYY3!_ZOR zIP0O7oAq;J5^oZVN_W_*YGRZ6Rdx8%WuZUk3&)sqjfNW2Z(WC!Qr9TRB-(%OJOC;j zYp1Y^YLHPEOFPO`VoOy^T+Q8k7wnmRP9>l&x8!R=l`$H{7``cBsY{Vwu3PvXA2C1|+!r40;#t{nLYTN~L5nXM>ak=4qiccs*DkYH@R8X$*&VXP0 zY=2DvQ>86{sw!4D2 z4M3ozRDKxt^gH~drbvhl#M_RGqb!zlY?k;gn^fNZcrJN;J1Kbde&6-^yeWNS`P_Mg zz;)GRvdeCOg2}ei$)VNQ-^Ax39s=n5^9V;FuhKqrLMM zxsm_X3I^O0K)q@m%TJ+j0<_2){va8_qu!^#%;esC(DrYBPm%8HMTh&_OxcDGjUJ%c%7v(HRqKa*5NIm@9c78#ydw}BhEaJe6>rHc`s)Zt0*D@iu8@) zd}z&ip0d6F8kLlw!!py3hsAqrYNNYmoJaqKaQG7S@Rb&|liFBRYwW60WUR4SYRlu+ z$ee4wH&&97sNqyu#fwK4Y^t&{S!WX`bJt0j9^KHBDh07!HOf*dJJTt7pjdEZqq9hu zvN+GSXv?HA#>LGx-|A}X+9()|4-?o}Zy2Xyp1+cmLprmor|zL#lWufv>fwLJ_B?OS zIay&lbrYhjQFZdBP_RPpalbDCbhXuuEUgF9h6aw5IXj~e~2nZ6ixx$q` z(Qm(C?C8-h?fUkB4t|M*K*@q;X#^mw#5rq|FX|V*%HP|zGqA%ft`Lq`8LzY>S@L#HoUQ9!_5=ocu8E(=ZPiKJ!!6=7hy4P@{s&tu1sW% zDOhzi+Fv^NqTKb#+CDjWZ+bZ7V;Xv=X=OD|Gs9{Udv|9brzw%XKhp(Yr@vBN-(GzF z$<<_uXU)$Dj5#!W3sa6a%N`M6nqDr+&p7Ii0*b<`9j1$&U@x8=HH%Akzh6kq6AQwR zL)k=2#E*c*;3nK)8lF&>cz_!2H)GUBHtss)=DZ2vnkU}5KYYtbyDHVG)DC5r))$ic zaOYmE{l@Q-3wu1K#w*o%Lv+v|1M#!$cs)8h8h4_qwwFO7BK&r%iYya$N*2}#Uc9`U z#4$Ewv=wel_*Q0`fzYz==+F38(YX8@H}Wii*uHS6a;Y1&4~y@5}y0}{e(Lf@Uwj# ziVl3D_}gTCY{AcDItM-_??@hte1?h3=l#)(KdUD1%+JIxCcT#dFZO4%&Lu9lGYlh5 z#w$0O5Es}MVcv_=heoWmlPWn-UM9_oob*^@;8hS)FD#=z^>7xj+b+=UKaGofzRI*` z-WM-1O za!r&=^u3!=)qOkxEkxt{?1LhX%|#gq{)Rt$tmx3dqNsT3!KbNOZiZb#jA80KT&zrg zG^AyA52B%l83d8gH+37lH|lk=bGJz16Z?s9bQ!!e1T-IM36|-&g^Tkp$mSgW%(*ML zHJPBqgsaxHz}|AV&3c6QqZ~&pl|KynS(i5$IQO|~MBRJ~ z5G9~o!~P{)7447hyogT(Dph?7UL=&X$z#lR;CDy!pXr0gd|4ZOKNp@ zE?Et<_;i@8lOj7`G5QAHbvS(Vbdx2Z!ferCu`L@Wym!1uT3d_ zD$^9SfEu$Xni!l1;Z76{RVkmXilj~@@DA?wF`(A(+YI5~fsj}&4Nb7H>+#tU7u1f< zcTxlne4IwFtO;@iM&Os`gYvPbP@l3U%@%*MTL(;%Ei8WyfxX9@e<{XEMZ+xleyV=1 z%!^sUZo@2nc$E5{n=Tp#1Kz&Q2O?*jri}Xtbcs)>(K~)4LT&}zO&v1|ut%h4_t8j{ z;0`BPn=+@>FRzDPOJ-j*0ixFzY#alEeB3S85@VrLkj5j9WNruDam;@#(7f>$UegJLUWB@Bix2O{VVFc!myZpyO3;3QO*%od zi>8j1pgB3kDcQNZMxlCe+=~sx zw@F6p8^jhDx-1juBsw{dS&nV62{xaE{psu^d67P76>NR9iD|tK7Lf~8$&rP1qr@PC zik+ouSRlROyAnlOKS%fmv{6Wu@-vU|WmkuMXqitNuC*ei$ zvKD=&oiO9m&r{2c35&Gri`2Ev5in*|Xa3A#6YI;#_YxuNH1xzbE^vBH{PO;}!H=An zs7%l}WF&*ICt~<)F@Q9otkfXEhKee=pJr0wZ=fHWB{cNQ*XaCPSxcGu>68W1P8-~J zLz7JTTI9`YHj+m_$#vgDx5(F)<~Nnujz>!L$O(Fo=3xo+t-T!0<86na`YA#ye-@&V zb+~?6b^SSPj4i-lr0rF_fkufX7}{D3<;kxWQK8A`bfwKPi2*uXtvP8iK?XV*Pf4sc zgG14CQa)cRX!kt1J5d)0%h&YpBaUH+(_ZVr%t$vU`DrNH7>{ot0EN}l!=2&3tU~PY z9;#mw5Vtn}h*gx1>^H#f;It~T*kh2P+v$uA_)`@dpiE`u;8K#5hI{T`SI4G2?(+-|0Y-_`F%ohlMRkilf^ zeMf;%THI+??KXlQ*me#=rGE>YnlnvMqnoUs{TDe~DSw4r@W% z>i;zG5*K|{YjizPV+Uqr*PPFPj<#OB-I@+JiumCY5uSy5yi0&=x;cR24+tV#?O#M* zyuK(Fy{#_CoTV(Pc-B-Um@&z1UPEostRL|DHH{b5M$>BOikR=sV%?o3G9DGiZRFwR z5%*leN5phB@WvXnfkn1JH{uUbrs`ZrKjtzZc>i}gW&#VX!Pmbcf*xoiPgQ&!v;cYbu0)9@HO_cV{^l&lk)&SC;}IC z{mJQhNTC|oY6)v=Y0&KIb9Rb67kZuP98q#cu=Zuu0C?YDfCcr$K^92_UH} zcO+{uC^>{-D_Q0jB^NT%u-o0Z%d4Z+6*T4N>7#>mNPFxJ=_2$%Q!RWXWUNbf4ra;X zDZ^76f{+9VY^SS*%Hr3v^AM~Y)5wODY@VEuS-l3CL|Fyw>sn{{UBNa>9dKvnRc z%qug+@AU8^K=yGf+%|q@fF&Vz&C1b%CJTsQz+n@YF#Zu=T|ev9S6VN#Nh1s>;D)L! zRbcZFA>_4ll!FveXb@bIMlke?RZ;TF&M7Vr&nQ|V$4jD`WE@eo@UqCez;c9hd=q7y zt9#65n|!}_KpgOHU06WI!1svIfo@rPKbW3^-3M_`;DO)49SJTXPM9~HFPo0nQDuR3zjD?;2!(C=d2Y4ur9|#qHH5*#r!e2{G^JIK-pjlxAjZSR*6k6C?Jm8 zWaF=Vr&Qiz&~WI_qjK}5 zqbHF(uZw&X8K+8Pk?%t}mK>TOE6;1q{Vs|lHTIqUIOSRu+Lece#M6j-f)k1F`%$p5 zm=?x#3hb>{9khiJMHHeJue5`*3oR#jGcae?ppcVJ*Ox$oDs=+3;JWF^{Up3F8IDwX zbH%sl0Rp=lPor0;m#V?W{{-e1z_~*$U1rMKR@pixg0RS;EMijE0&Si( zt};47QsH1x_i90);QdE&QB)2-mKW1Tk7bv_!Qk~z<;K(48HS0>Ar&jffG5?fLE0q+ zQ+siQhJl~q*gF!*`6zmG=byoTlmcScG@xxX85FUfauunlkdWRe5}D0g1(=R`4}@nb z<=bHd`aV|I6;%j*@8gh9yU)o-ntX@?q=aOfrDKrs4Vl$0vss_C;zsqh@<3t`lDKc5yS5;JuZ$?zJ6}JYS64ZBxx$! z@?oIah+_Q~pmD5*^HEt#GvTfrQib1cUrm^vUEHJF0M5 zYY`9&(SV$~(dJKnCVqKh#`_NSbuE!|f z?9Wr0;YZx6+t&bjb2K=TEjwOp_vwP@nR#XjN26`&ZGN&Dv5bAym1eE}l7p<*TUn^; zs(%L23u%UB;4!sJBanVHiDlU5m`@>u>;Cb2R9Jd+M2;_i0KOjc9Zqe)te=Ct;Y$KO zMVgyH>;Cl!omnz1bsjBI-Jbz9O0s}LZ>hu^>p%BH^Z{ZdeyCmEKF8Kf0Z@bZLU2TC zFowd}I?b&^VOt(Fze!swD}Upcs)60|XFi}?(;7k&o>Bsh13i+QS`3h2El(weMJ{#q zY7B4WwO>0Oe=e`UT_w&Xi%?r|&_71}%^hVa5+dv}c^g%kiE>4nT4H5LGT`X8LJT@J zpAf@P_7CIucvJlRqJbb#0LMW9-HgXecP!1$wo8$$p><~GUnG#glU|z9Tl3(}hg}Vx z(4AbHK6Y=IHX7p$pgh%vdLzz|Ur)Kb-%)N> z%z)ui#OU1bgXf8)D9MEIDI(5|3*YcU?JSK?SNUI_v@V|ywC?Az{ei0Ah6nt=?JCPGC5}sb(nje1JX@wq==PZp!TxV~39@ z=-(8|pu-Y2FdKGi5a^nYE$Xk-lZnu#C+IoezE7^^?`=6=828G0_Ze}?#s6>+o7=7X zx4^V$;cFoA*POXmp*Xii1Ns0_brtc3AGe040$A=6}1v^njn zfUmfn_1zC>_g&oC_h|RcogYMiXV_5+i@#H(a%yVX#weoXV9ZZX+ol{m9xh>}K4*v1gZsTforIXU)s*h zsk8#lZOkAjcEFo~1P_Nn`dj0-;AYnm`LFfmei7+W`=hWyh*$cftNYIp$UMI_wygGr zo@0)Jgrwe_0d_=W0bYO1c9u;-xXh2sie$5w7Q7d1-+F{O{KS>$zU4Du<_*rgX;uBJ zE8P|ik9G<75phS$@y$9O}F-)(@oC|I)(p+|LI8-VQ&d?XB-b11I)_l z*H0$i+{2$u81?BG=2HobCeqkBuz0^6-eD=NPY~*?g+K%ZWRX$bWI`9OR^=^c$%Bv+ zNjfw?DRd~XtZ$woExNI1k*|!1mGtVvnF!~!sS~`e>& zu+iK5xGVRaXr`1?Vl4XzA76ZsZd_W^fP3B;XGLewu-7}uF+}9$-~K`P)a6pBMU_lUziJML?YWs~@J>%qsa-g3C;d z*2APsg<7IM1IM(RM2PE-tn~H)R`O|q4l8Md%9}N( zD3=dC%iP8W4=v+_fu+Ai$1hPaoB2$G-?X4X4e702N2$BZsTx00Ukc3+Ea7Hn){ty) z@1q{X6W8FLvWDjHTdv3w%sRYcOu_-UCUwnw>$62E7f9CkuSNXdV4_P1-Rr);mh}f3 z06_XTI>N-(*2%`!$=LB9ctlgmmQ5x-%E%4b6%N94IRT%5e4>PPN4e`l2e+T46yFeD zgS6PT59X+X4LZOX zYRwkIcxLoEpwI&J4&A@00+B*Zq@$s2igDgUYMqwI-B6(|ikVZ!$g2-?LbYk$J4CB!iF8B!}CeTM1ow&J>MZOET2UzO84 zVzCzA$II@!Uh7RB9h!bKl&KYusEP)vop>0OkR=rnKM~kh?2VL4FmEbA)Fl=I&7nEG zQT&j{Hl8J}Wu%&YlUoG4VY%%hK1^=Jbf~`5PC1cX7VtS^DUiN$8kGeepsvYuP%`-K zw!()>!Ky_4V1Fa?qR|jpVE`W2tl8HFn=CjUYcOP%A(pe%Kz1TA0s97M23O6TlT&T* zYK$uOobW5xi1a;j{V3zK<`xEw^$1NuzI;{iYSVJ6Gcd?A&>FKqbVhZdA201fpR{WI zo*GXa>yEk&lK0QdflI_6b&z8-;qKxDE6G`Tr>|n^ezol?MGGtQm}bgcdK%twQ`P7D zj>c>!rDw$LXM@4U5(V9OA7C2BMYLMTB*$`mLqXNjq`4|KkAy2ZK1Rbi&2zkkG9JTOhH|f=AdLm?!JLU%7Q?8_@NSl z&_#wIF2neBT()F6>1nWJS*TKzT>)RfSNZ1-X#wnbejrX_;XLVY(W(8g%!(Rd@dT}+ zTcr1|kj{v}fTsq9wYTT5f>Rro*PNkFjDlWo0LwWW!i^YML3#{5Y(F(STQQ^49;3Y+ zS%6f@{*x*!<}E#G2^?I0h$2h}F-+n$6GfQV6EntFx(T$A5om>HoSG%k$<6CbHf;rQ z5N3okBuIW(o(%G!3F@;)L?X3M> zRr!iP)XQ3P;yy9tgV|Do6Rw|g_BASNsS$ip`q`Bww_c(Q(gL5?l&+FwP4lt-6Ty_z z*xmi2%SR{H)tKY!wzD&|+^Xn&~arcGmQFq>u9HI=ENALHq!6pk_n z5j#srs4PI=$N#wBI;!pM+8TBrhn=kv?y^pRW=d@cr;#M)s19>eM1?X9Fus%iO&Mvd z9xrD(;bzg^Vpu6Zpe_*XSAATxjiBFRH+Ie5l$1zAPLyX$!m*cd{e&?+hHq$TARNkf zjmf8%e}OIFP9_M3*1!q4n*jz^fe<^mkMq&fvcpaA3`5BYJ;WgtJ|Vy0fN8Qt1nS%7gB8&_zpgtthTjz%mjCi}ijN+>lE$ezP3Yx7@laQpll3NXK zKX%?ckM-~_!nOQ-TC$wIYqQqx3>S87KxtSw8hMeaq*H6CF{4Rdp_Y;SLsTiO!|d%` zHZ2?&>kiT2J_eLoae3Gh?RY5fS<^)0gz@AzGHYw7nw;@E5f&mNnWF*RN7e)o zS0fu>*Bt}VBj@c$qL6-E+V9NJAeSnZfINryGdc}N9gjGQ@62#ehUxJyA!1qdD03KUVA{IMWPorJw9Lk%hR&b@_ zzQyWSzXc4piHEEHdP}3z=4pkl#%xrTr2ET}3eD6&ed{dNw!`BaHJ+p%wk>4QzNdu3 zxHNRH*d6}5>oW%h#UA7;y$3 z9TEtd0wq%4%T+B)V($gnDu3KE1VQK1ZPc~k)+?w{4$n`o3s>_DnbP?tWreQsaRh#z=I8n!FjLd+P&@Rz>iktXU%BtOFp z14o)X`T~BS&bu#z&}vma4Q59l8+!8GUH!3O&VJb=Z>km8c_K2so=k?wtEvGzW}-ls zo^#(_W%b?#bV^$ocTkFCp9$(D>M>-;dYht5|E3T!*^4evXUBBkBmC2)eh48cdW)2) z4D-#z+)e_vz~lqz+tX{44$FPQgN}7&OiV*u$~b19#t*pO4YSDp3XE9h;bTWW7a_f+ zJwWa8pL1PFcp8>O-C3_>(;BrLrv!oE!)7mwON6akD##X-6h)R3d{IRTT;RzvEDk)4 z+{fxJJZe-WezFYzXcRN9xf$IP;JTsD5#aHZwgBDYfMM83F3o%HAU-eQV`1@T3xth1 z_X^22*j>FJgL&>}Ew>JYD*~t4hgO);(?2NAaUZAq{_EiX@5G)^1@3y}ugfp~n+9S1 z+x7p)lKH;`fPbx+yW;9CKp0>`o&~o1&2C<=Llh*u)FlWk#qju6IdjLA7am|L- z1sZ~=E9BK+sGE=3s)uM%9-h`wlT>)*#bCq z7(auvk2%ylWS^*zRv=J02bZ_})P5<0#B23!I9wiH^7Fc2e2wUzGJdn4cAm(n{+->w z=l^$q;9o=lxT)bXU-`@Wr~Qq7{{j29Ha0TXr!%+KH#KIUwX-n|my;2N{qggkQLy4- zLJ9x?loS8}BnAoo_m2NBQkcIdAV&pJ0YJ?(_SxSKs42fRKLAi43;m)0SL^^NJ27=f z0Duebp9VD9k{<{Fs9K2&@hiFMTy2H7QY*W&eZBjBBu#jQ3q}b^yDbe8=%5b8k&`%NRH{mqO2gE$5MUj{@$N^!Hk5d1IV-aD$vt$P<$Q9uO|HAodJ zDxiQ!QJUgLH@$`4Q9w#SPAcUM1 zKiJ>iXN=#tf82Y|$sc=QcHTF0%{9w2pE=i?t((;!Kk)zN&NoL4xOVFSEB?=GU0KP-;(f6~(blTsX=12h(3O*ZhRG zZL7E5xka1{r6gJd7cLRl^oRQ~%4=X09s*N(=$J{Kx+rn=uKl{rZCW90uM;xsPluB-Iz4Vsnc&IG3I*6RLZ#WFW zj_aN?jCp3}5enpj@o&18$I!jWxLR3g8YCQ51{~z0#HJ3j%ZRocxe=Y{T7(eFg#E?0 zd#ZT32rGYW?#%rQJ{sfqBRbD^#Bb0hOAjAeiZ)|wv35d()s@jU@%77I4YKFK-S}7Y{%%76#1Hn2Qwk6bw->Vru+p(|iXJSEe!XCZm%fVU>XhpWR4sL2o zIQXla&s1wh=?oC3%40RZzW1fZhP6*Hv{Nz318l@c#Vz&yrMe%2|Isa?o075%b^KP6 z#Y3zK4BTr_V$+zR2e#)o5SFUu8yFVSQs-s%>NQUW^U`>?ZIeB_CDf@KaK>FJ*9QpF z6r@1fC~1!#q{-a4Ibxjhw#XUmt^ay5LvIJZSGL^ z_#){GghM~dL|zjj}YkYmB4*Ia1w$p`wDRcd7Rc5z#Oc2I`Hv;rLH#%xohD>o*t zqQ^Xcj^Us~&$zNo%+h%!*PdiUYfNP3Wj5@>PB|z8v!-HetDJ3I7$!VEyA6Qf_-|TH z{SIM)ussVpHO@woSY++lKK1vd(A#VSs`3bZU6!T$2<0sD7LKMJYqW@hP`}CBCZVeg zfkvD+xB33ALr(c)g@#&kCrl-!ArPlGy_dD#M*I?s-c^V}2)%xVJP4(d(Wv7=kgJ93j21W- z19(-{jm-cipf{c5&vtjMp!`C_SBz6;;4f+jw%)VnZhVNopH0+QC{Mvk{DPVZme1?v zb?I%}9P74L{NQY;i(OJk(^~4O|OS`{(3PjANWD5m2P8DOjBJRX@gDv-uUEYUAAZ-7|)vm{{^r6s~5c; zCEpiNH9M8f!s1432qkdB#I5N$#(Y}U{u7i;|4Qn9c z($ujcR}kb>lOg5V_PZes1(yRZ`6Wy&6DjxFFcS+pbz_Q})74Kg+*-{cQS$m)PdrM1 zA!mnf+P~v=NR#FQriy!i^CZ6>#&rG?qr@znuri>Ab70Pqm;!yC^D;Ap;nvl(x)Ii| zg{qCYEEgi{J*8;C{K?C=)6kk0nf_P{J^?9~D6p=r;9_}&|6aS4YZ8LU4!BH1jM>8i zP6jU{J=l?;ReO1*(eONdAx78W2fV6O{byeSxLdwT2XyEAMgJ2glPtnm&IF)PrN6BQ zVTBqq3tOeI4V5|daSA~qH5bexm=pVySyK@OA^y`4HyQc@(cyAF>qqk4u6?VtOwFBj2=|kK2D|sWYF(Ax>yX zd7Vd-qRXih@Nx)pkX3MFIn>HdiE`JbjQCPqr&#HXy^69W`@$)g>u*- zpq2;>TwA8_g@sKdI{#o@;2OAHyQFtsp))5MnJY>28Gjq<8VYw}L9ho!dGyS}`;LO# zpa#7Co$;39zp(vH{IoU3Is3<#y$Q4M)M8%_Qwm}A2GcOb9)Fl&Qch?XoG-kV%s9hY z>TI(*31o`e${g1aU*9l})WBDlEHc4Fa8!b7uS03oV7ikeM!5r8<2`@6+=1j^Qshht zRQIBfuCdelMxJg~njMH)f?K;U0IjJyZ(n4Q9&`4Pg99C45u1(mVb3};>z+Se47oPJ zq~G#UXEHlFC_l62JByjkELuu@G2dbP)0V?oWAxo@HP5W*8al|m2G!ulEx1tP$c_Uu z@1%%riW*gwhmVI${q-1UkyS%PC}vfz>(;u+o#7N8 zjqV7Qx0!%Bi;w$gPs&)R^6&_KLGY^FP1r%93HXc zS8bOtR8X$r0=ZhE`s)yOG)Hjb*Sj~z@&X?FBVLIkW{mdmy=w-_lyAMR}*iWU@%x7cYPNYbx0>TM=DB+oJ* zF;h}n>#nCY{4{-tUL@n%r@x1KQ+MGIRQ<-oEJn>teCRx8CNFUG*^Ha8^BuKr3!b`G zFI#=7ZZlC9UNk?!^AhcKJb2d9x8~Nlmo=`@s|+GRiELrIAf0a`zPjeyR)=!xNoJr8 zw7#6|BU-mb#rh7n*2;`mPaI3_Et5FsPwYx;pyavVroI$S%3@jVF=}SD=G(|gO<1SL z?mzav>Qo6g&TuQY*H;raZquk-bF+Lsxl)|=d^KN?GYRTzh+E=j&GnxDS+S!^;l|`) zC07P~IUh3QKBmQ5XqnNdTgoD5&__n*FWG6-&B99wjA4QV<90`|2Gbnw**Bf+K>2IN z&BXbqYNNido!+bCCzV!G-@E6BgbR4e1}S-t^(FaxX31;3m?=O@M50;Di6S!9@a0Un zx$6-Q)8HU{+_TOS1S2BVDLxqME>^&;-CP;-tpAN2f5eAMEXI(;0THWO&F6Xhtkm1IR3Q(C8gJ^tSANs)mEHD8ufcCdK3 zo0F?IaAV4WrYYoR>mLz7@`3kJ2FLLiWbtcBVJt?0O{h~OfwdlL@k``uQrOW;c$ud6 zdgje_#|IP&6*XwLX@(A$Z#Q3ty~Nk|Do3U$N_E3LSc9LXWtT~9b*{}dTqy4f#`hU~ z)`${${!K6MD?NExg;}dnjJYVTJj?frUskK!d|!a0qy`IT-ov99e-Md~++fAJ-xffo z>V4dIJ7Kf1>_n#=q0`9<;sALcDPh*FTg-14XxFq5$ z?bN>d-oj4Fi-m*xUU!D#g~DQ%G(4|XIyd7l#gS1hVDEz|GPv^ejmTS+M8M|6YHxbi z_g;1)6;GDJh7-bmVwMLn+)B9*WI{7&b)yAy>a?Q76pdKf#c8-;KWS>aae_!hsBd!9 zg=XROA|KHud)Q6~SATOfv{~UYdh)LKa6Pn_gsr|9N;Ae9&?o?@S?X;f3E4c%&K+zN zZc#)EXpZ2iEB1LIs2cC6J7Uz7ud@r~{3Q^oC3x#jt#6m6qlbBuiLu-njhL5uY!wM` zn36{QO=+pbb<>yA0of!ddaEf@M$DnSZU<|A#hb1Jp3pzOv^z8#QoQG z%P()SK+*c3c?)~ro$5P1pU8|@I>B_Ryr;Ipc#JxLlr-z)PtF`;=xvaBCyn?dgw!! z!OH=j#;~0tleI0o>pDFxC9y%7s5(-`;2yn^hF^-vy~1-i`P>N~aoT;tH4r^bR>9N`a#$3&y2lw332 z3#Pjb6cP2kCS4wmlG79->yQ5k(_2#$4YO=!9w9FD(hHnN3*3L6iYHePzgp#&Wqryi zV(-+(qzd``Ij&5=5H>h(k|p;q{c0}grt-BVu>n}3ZQ!6nmsLE_3Ojoe?geRa!|sgu z7P>f2vy=5aKB9RQaYrx7%4INU!of?XFVJ*{sjfmtBwC+PA;(Bdr2I8(VB>mBR-w(p z`TCQybvJLTyhmbsasH{PJH5s(A*VfsCFF2WRjF~?$?g3Wb;my(T)g7xb@u>Cb0`O` z$YE-HBW$UsZT6j(;~s%6(x)@WXY?m?oe6l^{en7KQGf$eTuCEMLi)%#MNa9 zTuAd}|0WZy@tP3n9oy0BY5Un3fe&PapEU`m%lo47JbGHU(QRtVmdz!49x7(gZJ?POX^*|L}cN%8D!*o1)Lc)LU+Ak}5onT?vgGY%V z6^hZ~4kiwXr2I3nU9(_@nZp0{zLya&Q3#{uo(%I5}pc*EDsPFB={(x{` zKu!f*Lg<(Kn%6n=;hR7@ceZp7CnUn>?v8U#4i2y&F*o89=6}1-pb8=T}jk*eB+UJ)>k$G!4eW`$U zlWhbZ7dlMSwm(5Nq*h3nF#Arir31-!t=>W-dHB0>|8ZG`xfxCVGIviD-syOu34Mj9 z{~`@{`^azk#=`WLknuRIZ(IU+!uTFwHp_XG&P?B+YT~_rhTq2q)r;vH&uo_mmwE%~ z{V?k&#uvHbMJUnM;%@7*rG#-M?vuJAy=v3*jUb(e7D{Vdb1$-)m)w zz<$L-KJ9LsRXp{txL4i1aU1?;v9*@qQSoPNfn@b~q6HlE4@DLDb3SYZ!@?7JwC>`{ z@wJV-*VMPIM>_16Uc#;M;^USD_0U8MO}vowlO@rp4u3qJZ&S*AqJyu2U$wqI@Jv#8 z&o&bqr-rIA1*E=IohOE<{c^b;+EZftu(1EQbdu+qjmq3=);-5*lmoK{PpXMtiNo0J zFqLI&5f}SlssY5A6gsO)Lt3e(&cA7bOjW1LbWjzG#%Kj+x)-7JWwmimRHI6_s%&+Y z^X+Mb4c7iGQ?by@5#w!i(qj#3*0lYuusgIspEc=dYOl2bLP_WtD+rYWQM zT;P`H?bsFWqM62PNlwEPUk)LT#W;(by#w(DKFN^xFscGcel>7led>Ee?7fV8WFe`3 z&nDF_;KOJ7NUCfEir=R<63$Y?UppB%92n^NHK%Im$m9_uyZsa7po*}8a?+Ouz@NGVnqGq%JoS~1H_`PT32 zd+Qw3E0o5~F$hX|*Oy-FU8wd4POm+T8%5YQwl@-OIsZaDvyb&As! z``66EmR!v_OnvvI^}cJg^}qP?RMC&{o{O>&mcwyPBY#QPalP4VVmnMrkXr^8|9;yr zfNd4)^qj*PAH`E`?D&s_^h{E4&P-X1z&Y9FIh;l+b2^YPS8i#=r@m-J``$w#4Aa;| ziZe*Vz|7j}z3rQCs$ton#2gU&N6IZ|Q8p2uS?{dvR+l_WM+|~GFyZ+(dvVSs2x4tj z>Q4R6^Jc_CP85Q0*UZzr$xol4i@_SFrW03SvHlusafZtpXnQV)pc!!4>$Z81uvVx; z{md3CckNu~R*fYQ0eysM6bH>^*UmbXofxo_#tXY_OAKEJ2>2@+3$P}kl zDRg_eCSL3!m$vNIQ}gVI=x2u4vWY=J&}d)>6EOcOD`LJF_eQvhm9CRp>w^8}ait-Y zOE(IqZbHwQI8ImS8d7Df>=M*zQ;5h>!T0tL>!G(VjO)K;H%>gVa8|#?I)vQQ?53TB zu((}Em3mw}^*Y{t7fQfAI>$<}07N^5IHm_?F8m;bI1F`YdU_DKCp2?yZl?bn;idny zu11|Kqv@J`owEk!z(4cCNj^QhpxHoxyd&v?_hkw|5FJ6NU+;Uh6LnoIg?rZo%;9GY ziN51VIfW5BL2SV%vjcH88sX_QYTt93tu6s%$m#;?nVm1SkD1#y{kYsVqjshOZu!a3 ztOvAbLi{_ZE7NI!7)%WJSld(W?!Gwm>D#D~k}PB&Vdc%n^J)JtyIKpUUQ7Nn8{Al| z@AWxSBNN)3N3;JA7sRM3v}cgRQ-H%$Mhz|Assf7^FEc&TvJaWACkGW5KeHrCRZI zC8G^_QwF#4P=+ zQ?F+Zpkfbpz;3YhSGPXCmyv~{`e!jxCd$}_<2)`jf}Y0KA^^){a$dMG!B&5{f*gQK za#MkVR)Dn)`xqA;Sz$(z37N}(-EwHsJ0aV5()K{obnI7GIaxD$<`ZEaX6LIax&d^A zvZA=d)Ct=&nl?g9bNFQgEnuyE+Vz`e#neUf|8t+enPo+&D*G-hr<{XtteI}$S?*Qw z%x?6Stl>=)OhXHjK4C4mZ0QEeuX2{n1<_Z(-%xThEWY&2+O>$oG$&|7!dMRXq3}`O zZ)fH(tr1Y1o$h~g z%@+2A)Yo5BMc59aYh!EOr7*=dsMwv2Pi=MW-2X)RiyLJ+vTS@s&HPtutoQz1T^iGs!qLoo5U%w3S~f&zZUZ68Aab@_Fpoeai0^0g!3~aW{D=j%}&TFUeWIH0lgLC)~W}ok{fdtMJu(2%VwyqdT7wnk9p!; z#r#}EhsQFglsY!PG|6bwlt2gmzt}n^Pprx&fXG3oJswN;AmH{{*&o4-=~Hzpn5^u2Ru+PoyqO9i^^`=;mHjTlxlkL6 zrlD?ZE|5}XZAZful!Erb%Nd`KY~kn7K`7K3M%U5fp8jQ7h3WE(X6Oi4{Y1ChBc9o_ z!17nhHHLhwYSn{Ajoa!t|5fh&C7iCY@wo>OiBbja9>ChGhrRy-NtR?M=DVQHw%SpL z0eIzzfhC8h@)i9{P2kox=qGfFTL*BjOTC3 zit%NQQ9Ddudjc3!Wj2wC+@)`?V5P*Z98=%R?t04MBYS5#a#zBam~(C6^iKT$u2 zig;OQ0G&zy_4w-%>=-qpopWA1ZVPiL)%{3SzAG=}kC)yxh32XP?9SV7*yG5w^dB zdIOH15ME!HCDBM2b6*EmGkl$L3ts8I6J>(ih{KdMY5`4lNUf&6Z1GnE+`!8FQ+dBg z;9+iRG)r0}_Q=Z_snh>J*sjC!2|+FaOKRoIkH47r`v;D^8=hk`sqAj{VFJc>^6SNux;C|7gr3(!{+&54A#noZl8crxjT50!xSFz z%MvEhp&n-Nuofry+~~YD0rMYY4J`YL zVN0Sw+{iHrjw!Q(*^eOju&V~iy79h1TarVywVmHMAMSA2*mBzjn?=SQ$o;Rwdz7_6 z&6rGEUHv+X>GD9NHWy0Mqrb}uzxt_*97?>TO8#frV7Z?(vW(l1wfh~j$y{}7Z*Ns| zOYjzy*7rJURdZ`4)jH?vI7)IRaB-MKnVpt3E0Z}LHvcOkHg^pxZuiWKgaUCF(wx0= z(lryKGNAr+Q`Q;ys4){?Kzd72Qy`OoRu^i}pq}dL1f98FuQ;@D`YvAqh03z&?&Fu@ z>U)JQPg0z4mVe|QP3EJicxgCW`HE;Q=rmz%!;J|p$#$S$(iQ*%xE^W@ zNSiXvlUE%*(BI``m$|qICmD@!IG%D1zt%S&k-;;pe?k_5;y!^rNKcU0lxhl{s5G`V;P(jw*uUEF1bu!xC^T#G4s{f1XP_>tV_Ls$%wn zIHSLf*>Cl`-Wz)7@`=N=QrQBwGcT1VI`6jygL<<-oyjWA8V$ZiNby)M5;;%YSf3&l zO}SaK)2mF{gjF;sNU8u2Pr+C9xhmf~fFKN+NzFw9=&C0r3yEjUO)}J=1r(o+aN1lX zjGYjy%O~JRMAz8|9YBR7!-!b@GFoWja`pAqCQjD$PA=y=3JOg+4^t2vSpIhiBB$H= zn8+={B6SU{KDE9BHey>~gA+}4zu;-&5sfYDa(2XMS4e}3ZtCR4-P_D}OYx<;I!Z$QQA zq=mo(L4mTEqkV7O5_NunfdAgV?^eAU786NuVuxQ4RSegD>f+>!FB2%H_$o#@p?!3% zneRMZ4v?jZ_l=?I)X_q8Xy$nEXiKNY z2JW!tb&MO#nEXxj3VZ|=%ICe(s|P z**9@|DIKYH3t$&k>~3`1MB-l?6<#K}eI6SvV%W7@^7ila0E&Gk5$}t^;`_gmg($h&j^2Zj z=sS&cgLyi%=t$VNoS6)VOYh2y|EbsgM3psvGP|{~m+NiMjn+=ohTNW)WOjxzR$ zjs1|6ckN|%8&y&GOHl#Hs~_+6X9UIF1l%B7v7-RlYX5nx8(wIm^iHX}P{Th#zgIfi zHFa18HjFc4%k^V^s^V*w_U#Kc<~{Yc4{uWYD_W&@d}GfR5XwCu#J`X^rER#|=5Bv+ z903{bw~5}B=3G3!?S=4^ba&M|lKZ^PTl-?KWOOj5l$`q;QnBK%GM&|aeQ2tU9JLG_ zkhUG40{E|&x9|_V95o(4-=mL8o-zxC_ z@OQb|N6%np=z%Zr+k|--yhdKFpD+nQI(|sb zt3+`c43d|aJ4Z=)tvOPvU^?Jje=nV-cz^v!EJD$%Zpf^xx?Ft2t^d-u-urdfTWZ^XBVp8@> z;lpE*ep@{uH+Ea<@xJbsdO8~nVqqJX3yh-{24dIuMFx_~X1!ftgvXB1LGzW<%v(enW30s3JLRN~rRX=F@Qq08KHvMk5Gb(w0)$9~!h1e{yCeo-sCBP8bO1n=A_K2bIyiX}Fy*dU zTlFWJTdS6aV-ySjd`h?(ET?R8szz2O2Q8Q{rXbb&MQUX+vLB$tJXYIh&%NXRkFaaM zGz@Nifdl4)e;s3!DEQ9jfNcmvi0dQ+a}DGk0RZneXgF579h^>Ft1WkcJxormDU-sk zSE|AE>6KkSbs%gWmGZ3Odm40SKVSx}MSCQ+59Ry1rtI+)16X!r1HKH__pg1n2c0ROTb_gL<*7alA5l~+sC+#a{FOTfY3^<^T?lV7?EInYAM*x zZisx^Vfr;2D>gJ=%k%&_kT~j#iL)^2|8!9rFCg#S1;G8qjH0t1y;8120*t|n(hzD~ zH5Larbdm^*U4Mo85CE8?{oUqO`T5}H+O@m7kf?076SJ_bRKScH6lZFl#PONWKo+N$c;5cL6AdT>sgB|*n5iGrx5&&<(%zHwZZPPSeR`e*PPchEc^Z2junlaq5i~Nn zajJSDrTka(LNJ>j#^U=584sxH&eUH0TyyS}k zk5dCQp40F)E6|Ajhacl9R*!!@p14jrc33QH=H?4+-jn7<-1{zyscy9Q9bOI!)P#WR=sW7u-5= z4Aq3mYa)!#?`uHWT!DM4)m-UAeTmFsD8t90X_Ii!q?Xax=psYP?RzLXp@7H#d#dAm zZ%Ra%6Zk=;d$m87=Cx_YT0CCy@+Y)=5LO81A#^y#`KSM*CN>GDOOuNS{84kgUysLS zBXN!&`W!;Aeec=A9p^%2ZhRticGsbO6GwK8kQ6@>Gm+NC1XKS za_&>rkWUXl@r@PommpG^1^!Wm3#xHTeJzyZmO49%h9>M~OA+3FSk4RaISTbv4x__g;1zQGU zVBE`S6G*FK_l@B|-`d9m=Idf(qhga0qJ?Em7xX&;uD=+p@S_B=bcE!dKF`h9d@05V zx~}xz&3EI($-eKa2_7ni7d=682H0}?e_z6x<@P+mE5vKp*^PTvkF*LH$}}&cKzaPi z6mDu^S~AQ$z)w*uwsYBx5Ag20b)()3bZm?KK`r4UqM&?5oYmDN1luX^%&fxY@${UO zh0wd-UEG}sX3)hJm&X}4A_NJ3YhxWJx7cKEu|Zx{pBh`G}r*H;DG9Q*^hl&Oal04)U+l zoTXVHnt;jtOE+p!bi~UBVjvh|Gq?YAxFvi7%Jbod_-d!+_>=7e4KER6EAQn5Y2obI zI{Okyyq72ld8x|{YuY?5z-rA7gQ&>@O>|t=$kNh=c^U~+iBA34js}F=(#0K;W?i0+ zl7~qo)xI}G7U0_((H7|=l(8VT_xSwxT+Wr08rJX$T-tnP(cyHRb66?OOa4uVV4S&% zY!t{QiIy3FXwogS+6%sZkMGl=bl$=ugjMsPG7Ag}CS#<4ov_Dh$_HyCKz88w%Xplc z`!d&W=?M7rJn@?T_&O1!ZQ5*WIm-}U(Dl#cc2DofnYJcLImYKNoqGClslJ%mZ0JK} zFwO@aXR{F+!rYhU>pAFpN)|G<-tsmp31Nkm;7{HIIw5_z^}X_ZEb2^whTR^0#UiW& zW8@YE7AxB#O-D!-6{^0r&BUJ6VG^aFcmXNh83jtc_MPSeh`80bb}u$Z{Lw78{Rt3_T&| z9zO#a!wEjoFH(Zx0C_LRt-e=YUq9rEJ;r8M?1qZ&JdQok6$(bpi!Afdg76c=NFJE(MgrQT8~M-Cn&%O?AG`aO$!r0O9*{{<7lkey`p+R%oIHU`%`S{EYQu z%kfH4?CbOv@E)MB5R-FkDgdnJp8-wmE~pchIsZ%`{Q5PTJ!)1Cn+6XX?7Z26wiLh< zUvkkjq5OU^4P8)WlX6dVvoO-Vp8%EaWxpkVe;Wa23bz+l)r@J{Y-9he{+KWp4gzt#xcY)cf26Q~nn*48L=kv-;A`z_jHb-5L=g$6tNlA_q2*vKBq?^% zQI+E+XT5fxHJ3}O^|yNH`3R2jRoR$HUwi(})JIDiGN3`R@>91|W}P0)RLi&cr%7Bp zKJ~)l$neUT*`y0QyiMHc)lSKHJFIR`Is6sFEvLm0{E$Fcn$TKxg!6a^T)VI;wV^4! zmtEJ_ZXdEd4cYf3TfeY+5}CH`RlhifLEEYgR110?j>dnAn9bHg^w1TKRi0n!q&IO)FAwq^In_f^4v zx(MfHxXl&^wfSnlh*=AC{?bRmwaw{e13;+|dE(5VmO ze98{x^uU?GBhhW;LW@?YS6SSG@6;G(rVDmsDp0)n_SY{4QUps9wofa6_dS0?_TZ#) zx7ibTYbCup@l-9G@bR ziSWcFD;^y~n~_NeOX^2OSunx*yMVlBTeEn6S_8h4G-LIsp^y6_L~4h&oQRq4h%Ha| zjb=yhb+PTKf+cbA8+VRNa{8J^mCH?ugtvkc;1@rHBR)6{`In?5Jjrn**!!;sU}(So z)+YSD0-ifrlR$r96ootX4LcPpYqIkdyBVa`tXRn{H34bJZAOGN7?%aB+BBG zy5s|J9s`@-pq?4>p`+9g&LV_#L@W$B6kCGhSw^kkgzDg#&APYHuPsjF!|!?P{6Tk@1Rq_~_?Uyh?5pFlNmy}Kcgp1Pk8F-=y31aHeLN={bOL`X z;OJS`D8v_&hv~1z{DZ*khh8bHQ#Og9Df7TIDkpzvo~aekt#DX|W%q(;ki?a0m{Gqk+rz9D+`mb$Rx2x2;oHMNed*58o{QG~JoG=pttW?G>YsWQ>Fn zVYP%4w~}NMtk8=>7$Abg*3={T`~Sq5Tz?g@nuS30-)CpH#k&4|JQ0YHePXkk0LPK` zDAr8wBF8gSm4?2G2ebtYFvx9++w?LwtqvSr{IG96$h&N|BP$uU{H6$ZT77|KQ?~JA z=+qF0Z19##`igAKYLTPqb&l#VGQBr&$9<==;D*G~Wy6txB(c#!a577L5D8m)p66<4 z%X$a_Lp=TP(wiY_+awIO&K`JI6IRE+U71sTLK4ocJGhMpx6C3xTel-e5Hot9N#pFYO1TSS1narN&9^VQj&n;Oj?--(g{2dW(d^e=$IbOuUsY6`V~ zX6sf2yv=UwOziKQxJ06h0tO_9rnPNI&!@%jf0qzK4t|{_YSU`_;;A^`L&*Q(vRV73 zr?(i|Ppk}F{S^~Yi}M#{aNcp%S+(;jx$~F1VEEi}qI{5=9@VmYXw^efgNSy~2jbwF zEN0>@3QqJsm>@e(;H>r|UOl{Zdarf2o_>qVQ4CRlZzIaZiiKRJ-VQGYs`3q9_ThJonf&lY z?#aCRN?2P$Uv}wuZPO^xpe(`d3MO19X|nmus+u@8$u2EwQn`-xMe!OLFB?-2t?L4Y zsYs?DERSeJgzt3;=Mna@kSI>*T^mwmX%`H(xUDXJ+wkTm{&LoE`f1xzt2Q9DjRgCLOKSy_4=6hs~hjP1$YDt4qq4h1ji8V zgI>3Q(-LAEPAO(g`h$IN8dsY?r&cwJW0`+0X8#oo<;86qx8#>l@tksk_+Z67QIdvJ zbLskUlDl8r4RFu{=HVx8I$4LfOuBJoPYgp9uev5Qqwzk(e=yH$>9CSCr(m6|t zvh?V#ZJ7ZXuL4wqAsCMh7JqJatiG@iZc1%k5c0B_96TpnQ{J9giH0S|Fpl@#n7N4w z{}azwJF!qmF3pPSxV{VJx@^VqZtLvCZ@)x=ra}GnjL4B!f=`MMR0#fo-UUr$;Y~ zwW8chPV`Id?R;7=6O#XG`faDyhu3q<{G@N7x=yvvS}2V1?}$&R?|rVU9tAtBD7%~! zNA9nFkxn@li5|Th)Uo`++UJF~y662i%m0phOsrL7C9(mSy47|aVhQ5k7mLK`nLN+JAO#{RJ6(qOeB^L^QW>rEK50nrz;x@fx z6hOf*7P>pRYB`4d9Yg|xhH#-Ai%YkgLepxWdkH(UOgJ!LM55)Tm50$Uc`w8n zXaT(R>Os9IcSqgpi(;~t@-fcr6P6H`7z8OKaOR~e=$Ms_RX5+iC)&DVzo2%LVhS@g z87jo+7)fx_Qxd}AEKIbU-$Hl@1q+|Uh_akWX41MviLtW~Y|3p7$v^pFrbBzoGAW!M z`or`PQd{y(ONAh)z_ev-eQ!Qho-n!Od2x_bIhiMfoF2j1crg$fIv})0FL%KFqQc+w z?*+rV&C+qJ5pVi;4-tMCTFm%7p;iqL!TF16bwar<^r?v$s-euf(r-_qO_oLeqfm zrjq^5B}f0jF!xZiN<{CIH{#&Wc^qFnIvv8O$qj!yo9zVlc@6D6a7i$L^2n+6!W_1N z_PU#1GQ0f1{DfwR%l7s{^Hxv)smS`p*grygwt-DZXY`7SZ_eqrO1w7?Zi3VHzj_yh1UU2kMx%FyAGb}kKHaBSzKjd_5D-RM zN~A)P>|UGEDg(t{i01XqTrlI^d+%wL;2?;6@jn_Q-uG%q`C&))g~crBPInF(#V zROn~hQJjy|YNrSd>3@?6c}sb#fu?Es8gP=j;+6`xqi&8wN7;wL zJ^o|t+q(26WzB@|qXo}%yKqb&!;h3^-+1y;g>y*BOyCTnfhrkAbF$oa;!Qu=!x9+3 z-pL$k=>F(OI}p*~gkCwxwC%ZGWmzvuJu9XEFcqj7m~I<-nl2{!!Y<1!!T3zIEA^LL z{>)n8fkN)4Mo3g^t`{p5S7=s4eEetxuont5WRnT&-A!`^Ml>+VXpNq!D_Wd6r^5BR z%nBONnYt6zuK}y}YK5=izFpRGX_cJAK^ZSThvHk*I0|_;@$&uMNT?%5`W=5%8Jgx| zg7bLTTPWy4aVg*h;6nYKuNec?PD9TH#%`xUHIDVwVM^pti98wB9M5EewoC3C+GaYWxU+keQWzkOrQ%vod zZvJyOmQ#*>p>85iL5QV7~H?DXA7FiZgSoCW zgD5@K_eM)0dtAQa$_{au>OVN)((X#t8JTnf%nh3Afpj8*k-t-Rd-TkL#T=g0nNrKa z;StxZhmhPGjtBUAep!K?45(bpR*Is9lD46N2W?!}mS=tuneh4YK`|i8kAl{^Cm~eWz6Chg{ zfV=MtH%Q(U@Bnzr`CX`h%O!&a&ZotYI^aYS7y6QNPTJ1B)Md%2TW^OIo|6n>*#I9=&`ZP1D7a)+8MvL!hodBh_bCq6F}#5pnEaW>pSvW0Ub019T<3Sn z`9n6xE6t2!r^`@*e8;DgFU08DJ5NO0HKCVO&vbW`knienm}24PrQsQq9p)-;=xWO~ zXF|af&TY>E}goMFF;1wn{s++Whw@ zt`8;OWesOMrzTWQc61NL&xb^mhJT9YtC0;IQ|u!$vInk{J8v~LhM;LV?RSs!g!aD~ zEg9t7T-c1wx=LLrdD$2mAR4$nsi9lOM-3cRAG%W?`<%KQc6!^xoqa^bj?Mk5Lh;@SO+&HOo8uHfdw0-m>!5 zrC-YellU2X8Q}o+vO!vmF1Mula*E=786wV{>nnPk%ZePsyj&NGqG(*eMsi3o#}rKtEd?=hP}c zm{)c%WC#-+ZlT~+{g#6EM~qpvE>OO&8d_hTvq|Cd_w%(i84?kQmqOg+A1Yi_1;3V3 z-wTM5?8CA;MOm>E{C5!JpQ_yg1;aC*4${i7E3dzW9E%Sx%+giEIt!qUNmpmwY~^d0 zR-TGU+EKuC-=k7W#WRP%g^cmKcxupqg5WGwoq8X?^`x`kT!LJE>fmJWmAeP!j@l|I z8%eAQDb}lk@n1p3AhqIN8YY!g56u_;qu`bF%5$l`S1<17e<6mgx;}eXjK5wR_PTg9 zTxksXE_f6X&4~xlwyY~Q^SP~$Xj6AX1?8^gPuEeP8wN@3?v$6Y{(Ctcn3Tn&=!=a1 zMc#XW<@~mPz*$+zN>rqhQK)+l8YH_^vLcaGq9F~DEhO1wME0nxNF+&AMnqOfs3@zF zt4$I#zRx41zK8OokAXYYO{i^3J;}gi@AKbe! zByFqTomuU=dbif!c>Fzo^}^U=*N4Vz#cA{L;tB5g`Q>BFl6$||(pETV-#x8;-f0K; z7f1MRTj1e5|H5$IgxX>Y9UC|8g|W`J`wTub+I`|IB>cO47Z|7>>U6M0`nEgo);xrZ z#^F@zPP;2r7wm6r&W_;i6S}O49mj7wrxOu|3^3PGh`KiG#gJ#A)zm2oP{Wr8-jW%2U=CmQ{ zaQi+RpN>#_lcf^q6t3rvs|ot5=LXI_`mOi#h3yx{=1vM7Sy$%oT^yR%SdZw(m006E zt0~8C6o>D$cAg(Q*C;{~l;bc23BkfsU&?d4)Rb3Nj7TvCU*@D84sO?K(!;984KedkAmi=X} z?#D={n#c}OHz+-{+U{vvIj>&H3T0Ot+0E>ENf#g2c$)ZxRD{%t930WNyxnQt#KP~U zHc5VtYWFTJ2v++tfM;NRu&e6cXtg&{^EUD8;X7G=v`riRU}i*Gal5@5`FGpYtB$pJ z9d_et)xm%sp_eT04qP9Q;y!=U`Lqz{;-2Cnb+Pe?Ui$Ds<9Q z*(Ti+`k#kUhsDSE$gcEhyib-%Cq~`S-l@#QR8*QCJPZ!@&{nuWl?4sxQ{!IBQdOlX zeZ}6bdp@!+nQ68?xnx^q;yl#yeUt|ZD(CB>GrmCd_bHT_x{Br+EVx(M3{dGoADA1Y z{Eju1F@3P~8s&FXsT^An{AJnrs8oblq3iMjtDp}bbQD*tzV=_S6KF{C*4*MkG)m-2uQ{|}s8 z{Ey!*sr+t7kn!JtU+Mp;Zxub1|8i&BnlAtUR0DjK>CuLk^}%iS-x%xv2UM^2R1HgZ zNbo(dSUOKzvA)X0N}q2ZYMTG3AFsV3Fy%P?dT{??)<>)+F0vn5ufFmNZ}n9qUgbIM zM&iq)@TU!)V*hy1FW&@=f6q18(+q#~g1y{lTKR>T8}RgadSbcQL?D&u$@nHRiBM{w zhwsbx4KO#D5*RqWm#OK@nKMmh3QhcGc$x}$Jf11uS3J@y#M{ev+CM$Wm&@g*Y}7!{ zFW7yChqvEMJm)oevb(Q=h1q0xj{plZQ|^bSp4Vh^0~^;_?lUF`F!(Fx_oUyTXgnDA0317w&>WTP569G^3pO|kV;z>mq=b7+C82=LsO@v~;6ypLD zkwl^={u7H##NuDy{}W41q!K>nSJK1bv45qVFENqJ#d>0?iA*f#_Y?6+tKms@AC&u(*fdb1gJ(lMS1?+v9OvcFLiNtJr0iGlBC5-h_6F#>aiGZlV z>QXF6!vcEzx807lt^I|>L@X3x_jr(Y4=4~}d5|ln<;5m4Y>&e zF}+~$?^u@Xj6;vnwV5dWFlb31H>j1iW%3qK?SWs@RAQED`|ngNraNW?^DP# z5z)*{m&N@=LhO?V0RRgiN5XP28dxqR0SaNpFh}e|!V`c_C06cRh6m zSOPXfw|S(!3^VPOF-yfTc7)N|@%W-H+Ua4~GWRmk7nQUpdwXfy@zteV~Ut3+#iB zpA3pQOv=kJStofC_{&=I31b-&mXwSH(+SxA1twxrCb0>Oo92V^Pz(Y25gi4+~42^v9A3_}Fz5QqYkT>#4|ywNy^g%S!GKc6RZY-+Jm??rF~6eIq` zaC98`7zYcmJiIH-$A>dlkS_u&MUWJYGxEVYU>+POVWb*hog9vqZJ+!pgBqe2eL zW!Nk|&0(QLMuGxn!yNGnsH9a0YlC=eX;$f!tXH5rz`+$V;TnTh$%jR%`8;H1M(DNkx;zAxD1vm5`mwb`;v&* zI5GiZ8z~x_AF7KD1B+#BemDS%jZj=XfcI(7jO{WpJTAjzA@FiCC@2ksIuT+Xc^iUw ziY z4fZiN1I!W_5(jaOFl8{bJlq?H>G|LG3cslQ1*Ah64Bm&Sk(YriMOcg?gfe|0NRd^M zz7Qn9B`-iCG6O~O!XSHAv5#6KXUPkO5!8rnT=GKuAi9;=3&=vgM%1w61zZ(lJ2WXv zUVufE7b=q%U{Y~lE_wNhx#Z>N^9pJd?<Rko&=(>353wBf1I7Wf>_v$c*$W__tc*d3Q-(09 zKzLWNHblX)7bS+S{cH9j!HkrfLa~^UNMOetbDvAYnu_ zq8=U>yM*kKV#97|H>BOrAIgFmRzhMHhLK}%!-~y|l;gBHL}_wvn2wl6%wPc*89j3RjwP8Y)HVMH;KrP+*qTC^eK}wiTwKKOv`~41>pD zHPcXrIrqvmlrX2Cl=A?Qf++P9wDP5(RjHqZe^F0BsQFLz#8$*|rT&4-v^Ar z${91%7o4MUDh1d$Brb*5CgWA=C$ORv;Pew%Mnyj22GdVa3rQ+*Kcb#xTqw4ge&TtS zi!=SC`G1on^poue1afMC2SiBeupy;_5~c_a1r@m{S z@$eVWhCnSQPlxsvpM(LK2_$@*TC5BpGGZ1z&Q*aNguCU^55?PmVu((Ty0#x%jYNqe zP$0qTL=E|A7!B$chWTXrz#>XlrBYXtBPWBtvx!Wr>8@a`2QeVg?~- za$wYyV3=8zlr=FJW}ZzdqOu9IlLkUlB~)brGCz+^ahQApGaSB@sD*6MLL?Ie;xEabz1g>W1wTo6vN5nyN?Ff5C-3Ti^FC4%JZ zh)q<%5hf(&(Kgs9W1>(_;f64Y40B3F0%)uP*QzL{`85T?;tW$JERleCg(pQ6Bb5ZL zJSyd>)TPoNn1lf=7Lw3_6_vG^RFh#!c`*+8Gc2dh17Wsz@Qj33i#blxDwIhm7|Rg@ z;d>W^A4`j!uAVD@jZWLHXx&%u>@L^a=`73Mp(Dqs!u4w9^sEB?byhJCoTP>y1j5vcha7;k`%kRU z_S)zF6Qdy`?57;30~uCW@Q`zmUcd+896`)Ou0iD!`XyknjS{0SMA)H^HC{u-1NB>;XZFwR_%#p3;1%)&0sC!mzPmOKTn-HzhU!`5%yl0x=G_)QU(F9+ljiLck2d1Q;R^gSnc5cPCr{Cwff5 zS2{fUb#=wtN_|w$hKiOa>Q_5CvN;~98ivUY64#|v7y~oua@Yo`9h<#&n1i(%Nk;>a z0f#X=n2pnupBk&!D$&FmlnM+dhq~bJY!$%HYo`V`VH@N~l}rD`oHYO=e9<@(BjN{A zeo8l?{bH)%fm!aNv<4h7$`sZir_7HJ^(42&tN}lPslU!y1F;*JE5)I?_&poPbIcq# zjO*Ax&4K3Qsz}a{(BT(%Zt*)-ngho9l*oZY5-MhaC5mGOYBv)7LQ3qwVP+1%B3h5s zo0{5$sr^Wp%mJ8b6Jat3@JC2y^$TYFRN8}*pGt#JVx^iX#KFOTksOXU>1eoCRNyJK zuKvWeQ!AeT6C+=ub3b%Tk@M0PU{|akfl7k%KyZjUXJR@c;#@TAoYi8=3^0rX8XRO& zh@^u*f;>_P<)~tDN<&C>u^1!%f6+QaR{&E%R-|J%BZpuhBc_9_F+qgLDS3ghvBVT- zGpsjCMGR}Z05ihSI-`Byb;Z8qREjC5r&NgbPLLFEsT8tl+5z}VDnX42+CR<^=ztEn z0$OJrBnv>r##&%B%wZ@8*vTHpG@ynZh%t=cD_Uo4dLjoJ!8*~j9OaE%FT#e%6_fFE znC+f!ApoOyM&yITv>x@&h&mWBen9_N-s??mhu)O|Z=?5+|08f*H_ZHXGM( z1X)zt4QrhdW~btc)&xB-r0zd!O@K-zvpHFgwPo8Q;US@7 zNB?*MhigiV?E+WyHJm6vQ3cRtt+8K^d{X4GD3K_V-}q4>`WGR%AP`L)+V z2C)C=hRn|^EpRJTjzdS#yvAWGG@QD+z;dcJ;PvQ89#}$WPIQ$4ok_w13f_cK3gU1K zB?rO{U2VWW2t^plHRc5g*cVQyki603sMo3TL-7C=$4}@Mtpbgt#veh-@sT<3WfB`& zoh8K_#;py?c-Xj~rc|;Yc=kDFmi4TV{O63tXW65LF`4+5FMzjITsWSmg zo|di%phQuOBO@gbOFbw$@Sq>)l~JbxUX8Ab(Def$oCPh;hQT7@B*G9`B@K(|UJPg_ z`yfoMJ38s(qZ%WODw(hVv4LSa*bsujRDIEMQl#0yIAr56ZaWdiVIA!X2?6W}7`!6# zunnvaqnA-5mUbsW)(0$+63>8nC`5qa4rxA0&)B$t+Bn2Z{6&wP0?s>uQBFgmzz91e zNqBo&4-#ajr@)ln!D^)F&|n;H0jdp*yHQvlm=6tt9ieed^OJ1xJjz8RxeVfIxH{_J zGl=vB4pk-~4Io8<_a=u6PBDzW5Me&+3u1Kv8aVL%I61*EWjQiE7Fg(Qbcc|^k?RLv zfWdS^d>rnxokM-}aIZm5%}(MB-HRYBp}sdbFF-&6h7E9%qqXrq(sAM>c7jX*YeTa1 zKCuk6L-}~rpT>(~_*Lvhz-M{~5uhL=8|Mq8>>3O8fE5@v2ag2e@jQwJnh%NwI)Ft? zaBvbZK|mZYa)PGAc`U~P0ZARFWO6DWu@`C=0kfFE>=`u#8)vj}p%oZs+Qc|?Nw7yu z&ZCwGVam2>ZK{%)R)B+`mD;upg5N|st`KqPgb&Vx)KEp|4yugaUlo*!=OoY;1RX~Ou9rmPjV)m<8&NBAI_UR zIouP*X&$DctiJ@oQOX1r=Cl431V@ZTAYm2&NXaiS>RjR_G#r*m79*tN6kxP!nFXQ` z%`luMSui>RL=6-MmH;f5Dc)xnYN+lZFGcT@KEN<#%pM zpP^%9=9aKa8i(tp4g*sg{D50woo;9icN>7xYCHzs{B-O;Bg#L;0Fo}d{sCjeJ{1u9 z&My9en^ai;0pS(tl#wnU;8p>a`yE1Hzf1)p0so1J+OUSJC(!c$%fcEC!boHaGALz% z;NUT7n;fbggF}J}{{hKafQfh%Z5ie|fH0|;bK!6twNUY>gsdE1fqPMbl(YUy&Ofo? zXFNt%or-w82pz$R9{S*P*@CbXy35-5jPD?Y+G#jYky9{(g~`a((FqSd2aA+ZCz~Ch zkmbQb={+2PFbB%6%HTbmy^#SUj7v~#Dgr#oX|QW49i3^0cQmJ zQl`qarH=f;8zy8LnqGH`xE z;vq2UGS-)|(_4}SmIoA59Ke}_ko*O+XW|ud9a;{rbB6>7m8d*{x%M><&!{a<%%ekJ zI!q&{2P~0Oga@~zaOfZ(q9o?R(#coDOG#@n#=$*OH+r02Cl^ZrnWlrQMuLHEIE^DS zCYjNuXg26Pod{5R!d^#XfZglG<|tlcH!Rp;%aq_@uT-m$p0L;00Scx;@&nOxyrXgO zQi%2h@meuG&f$r-n@X zhAr@M+(CO`9u`Thl#&nVEwdSzzZ3#uM{(rH$tKBGf4AAc+c#^6gCPpfq=3*VcDWB{ zM~bxPSEz8kxY2`i1?FP7Tjv~9zAHz8#HlBHR{{T6h=YG6ks57$@&(u))dZ}uNAU@` zw$Dgx0Xx{WSaewBWS$h{sdPd^m)#^`E(paA8pw1JhPfa*bVCDK#_5U_?EqkyDd|0Y z5Nj$aF<8VJjEd)(6N343^TDiPhg_eNTMupoH3f<(1wG`2a8wFR*9njfQuu{sz?Xnf zu(1}y?VyIKeIB_d(ZS}UegkE`aJ6hb>Z2mNguybqCZ7o=z`$9ii~V4JC1ySq>nomz zVPYKgv-zNAut7 zPs3iglLjPI6hx54(M>o~0AydBAEW63DNBPl=Tira4YRxowhQ{HLr-oTRFF9lhM+h{ z2QwpbSVe_huv?LgJopTdPe-C0`QR7GCj=8BZ;eWVvM+A)jN5#6&q=A>Rqz&9P zVmN1bYqUF|hVU8mKAj^`=a_0q%uZRJ7}AiC524lRm<7@z?FEKS5Ta^`IccJusvx|@ z?TuswCQyijGiG}yOnYU#L92(2D`No7r+A;SmRpU@Pps#b6H@6xo{U(_h{NLN0pND zVGeSs5_=Y5RDm!r72#kY$k3#r^dT%f}^`cx9DvC$Bq7)qFKP*T8ReEQvk#akB9~0TcH9d8mt9humH7=E zUtlhr<5I1NxrB7bhOnYykkVZ^A%YK|OD%Cvjl;Aip2tBOZ38j^Dd^Ktca|W_I9a+v zDvr(tkw#?-c>}@}`st1UV=^7cGmO%Zw!-{1(r6eSYka`|sYaxRg5;O8gny`5a9{~5>9@NC$#w$BL%~9RQ)j0S*gY_942jr8ry6=DxVRk=*AFXy5hV?McDF_aQ#cX>*PO@l4K~fm!Zf3J`7f25IL2l?ADR#@rQOtFqC?VzI z34K#^nbcz@^;LM16L<%Py+{6W57)a@eHeOf4ZR>U#jwfeQ_jKgMjh6v%j?fD)mQv4UCvv^afg zgiS`80b4>}ja!_O6=*3nb%+?`cZ5;8DmDX`0*r1Q83i^+ML9%`n8!9(izTF47>7+m zHSmcD5-pM!svY1#sD`2S(M4idKnW`_`}6?$c{B|Oqg%z^S8DDrn*4WzrmG56(?Jl_ zpg_LPTHwIADnzv4FOPkr1#X z7cXEIWM8mrYA3-T!=qxH43}Xc?4Mx)b+_<7$7ImJSpb48kp>#b^$_EjN&$184&j6} zm@r!(#fTj1p^_sZldFUBuyJZvfl+i>i!ilBfKiG`+57AQ6j;Go9M-3vDzS>O0`!xG zLCH9OfWOc&D3SZ|OB zW)2H0ljw-85-}r?1w6vcKN4k(A-Lm535rIKSIT$8WCyX8_-iP@>FuV#whd>W)&JX@)Ki4FkSIF z_AO+&m|}JgGi?x~CxF*!oHI}GQ-MJ_vCrg^wHj!hUodzCC8FnR#}yd04FyZ)OrZ?O@6oX)T|?#!J3s=D zjD^N2CBgf@j8i7VFnb@&AzV8@81NH=55Hl|2knAo|D+!s2k@DsX$N;vXY;OdL%&LH*5-{l>GsdJQyZb!0L2Qmxifbgg(B2ZdVYd4^jh5DdYfB zGn?A{xb4TNrcMkr1BS(66e@r`N-Zgv(wP}yx=28nn;b0VhVkhrS{(6<8;0nxWi%y; zHk~!_j#dO@VY1+kvegjMi9G1ZwC$&NV^|3G+yG zP?@nFE|qMgdFZiG%Y#*XP;c~*P|QHp@H~}#m>#tgy|2)7EXOi_V7iM(%hNXn&^Vn% z6Q<0A@K5UgWg{>SBp)buahDbuDY}zX+pse-=Dd-)KRKN{3@Z5a?L9PN|q5z}Hq)*_&8 zCtgPgrB7PocgX$lI*g9EL$4EUFe>^mJz??*7{(PR#p=}8z+0>g0daIY8JH!EcwCG< z&`C2sN`luh4_E?q;T#?IDyG#*eVCA-mt1EUm*=>>qmm@`YBd{wh=G5P8p9Z6r*e!dGe|VY=C()JA;N9To-(Ic+4!YCwfH z(&KbZTB(gVJAkw>y+RuqMhTV^C>6!;Yrj5YJ zy%}cO$P!N|gi;$ZE+-G6)JDRH0!nS9$4P=pZA3Q5qe}w_222})=@LIMX(J_nr1}bF zBuII-LK)E^gib?#3S}hB)I*_+bix9k-bV}DHM@rrW{PEh-_hUY)VB0 zT{!O`Od3P4)6s}h5$SRIc7j?(r1bo!8UoR+AR1+ANGU9!LJNT?O4n*3Fol*{ErdX* zbgtFH-vsn8vV`2>>+mZ3I1q(eqT>%psT~W{)TCvbz@;UaK5{n&mZHtW0;`O3=J-Y> zC>ug2ow#8iC^sn<{O>m_PSyy4FC!O*I6`eeVmh6c{@favOo=+TRVD;* zgXTjj0gph>lZhb0(g!rieCbP3eqz`kCp5)4iapW+YRytugl_&q-{1GbVguGdB!yc= zqymHCAKA$+B}2eM%6q_mgdOIdd6Y{*q1p8pyiaEkpbfzS^U!>B0tJ%fBqv~no#2}v zxM7rK80ItIh+#UW1nDwmETRK8V0?mtygS?kl!7^bAUFeZfyiEYl$&7`M%ll0gR=j8pWJg=ijJx-~jLP%_@r0xnK zF;@I!{J#o`&J^iD4BsEnk7E~) zQ_QDC9nT}uM}k3A zVpqr=K`m}I5;++DDT%*Ijm(g4>L{f~W=l8NiBTjq!a_DDCpGd4Vw5phn@NqCJsof; zq(+#&T1F`~!u07BPEP1A{)W*}0;Ufa((|m>Oim06s1O?~I;c^tkQ=!?u!u0pjUavZ z9IZ}rBkG{Oj2PyVz(8uOabn|MR6}X;HbyInm&8t) zFKDN7Unw@i?4#}qu@Pn$WHBvQpMlrpt`u@(E|6&-C$~Rj@prjV%0x8|**EJ_aa|?2 z9kf@-E->)xI15CGG|J}{7>)tQxO~Wg0u?6*cKw#>4o*eEZxRSMKb>^q;|IV<2RU9S z#^FQRsR+_piYnw&33Kx!A>puMeKfjJiO}suNSeNDrxs!vv7*eTD-`fVzhkP4D6_-< zsK$d{;h>DVfpnvbiaRI(%lzOTs1HcF0n6l=ttytIK0EY`1$auoxPvyul}|)7+8O~Gy=6Cs8y~8&EWCmv^?Asxqa>z zEC*sYBZx|9EagCSlFH^IZlY*Jx(7__ftw1oz__1SN#(G8!zTS|Dg1T%hUn9&!N3KAz z8i)T$txY}gC8YERUukMVU!|&{qN>seU!1D15;=e4Ff06fjw5w``6|`GDegY*rtJUu zCis%nH%@*1@+BJQn%|$V7dW%$gLUo=gPM#>$UkDMBl23N>$N=TzWwfj4ud0iZ~D|$ zEr0H^Iw@)es)sJBU;pHJrrGP@7ZchI9-K7g_KT$beVQ*{7(VCQgBi(ccAoz(k!{h~W{jcmdm zL~FI|v~9oM&RN=%CY6m6Muvac^v%o_?=x?pH>b7q-N5Y*o?do!Cbo-c;FDAE zD!BKN36rMn-0gMD{GIuM_`+$Q@^wPeI?dO1Ss1Z@+S~puddwYFuZ``Er^O2`UwDNy zFy6Y|_D7QNQ{dUUnFa?hMc*55*)D15hgO1p&z80<|M<{%=9mkk4|?=m-sJm+X-49S z?KB^3IzMp3%FNRdJ)g-M%;67oY-i#+QQdLt@*@Sumjz!6&OBL_Jj3_&;)%BLEtWi3 zI_Q>))8)6@J|DIV8yT-UE8xes>t9ztz5ccN(U@}~A3v0iz7dl;yDGJBrK*i*P-fb^ z*_EHAr5+PUH9R-(>sP#_NZdq?OWDXx3fKrOvP;8c~>A%_-L;QPW=AGLV7YWDeK2dAq({rGKd);peu{F3U^ z!?N53p**|h_S(@qXT)iL=w|0xlI-MisbKNj3{lWA&1()8ZNEm^c3Irct4H(jaMNwO zH2n20z2IA^o?O;$ns31+{XJUSXS{8(SU+RH>h^`pqk47Urz30W>a@90#FT009?Vx+ zx?%IRie0Tc7|nRHtxxzA-?Qog>a9DuZC=yso#a`<@@IvMmTbK!%*l8YZqJLbkJ=Jg zetgM*&zc*0Z)%)ur@4I9mS%8u0HND z;oh646T8-F%uNtjqTy9&xAZNsu8yN3hTn#K z(QGxW=87OlP9YmmKR@rW0rb5q|A@(b91?oE{I zEb}WLCm9@GdvIU$u-#Eh`H?rzd|BjHFmB0+Jzd8q48B-#ZuOY#;m1em42t`FKK69! z!x&!c3$vbVjp(#{N|yjv)Ae^-uXB2M%F3lzRn3SfdG;6GYejYUFOB%vb#o`xM)-2) zyo-HqwUHipo-LlY^V#fA%Sz=zu?@9s4|IDr&M^K+yY7+g20m%oqqnH--sZ(GG;9Xt zl=m&{c4Ow=dY!ga?vr-9HuXyCyP~nl#(w=TcDFTJHd*)LjDfLj-`73zaA>cSLEZMh zj=xp?d{P&o*4~+qgPt5QY&f-lK(?yEOxu9(A@if>th^gnKJ8G%tyGuSvqv7zPa1l* zda3=Mq^&l4x@hSfJiNEp$);v4?(848za+`LX!p#;9}jKRu?{*jB6VeRwZM0SCjW?$ zC6?@6^H!MaxS_P6jffgocc0K-R2crn-U8_L5aBHK&rtXrVA$?ldjGkwd z7%?MR)WzJshn;5l%6)r;m5~}F{IXYQAKW`~ym_&n=V?Flq|}8viD!4Z&P;W<=#vt> z=XJA(D;JiY+`hK-e(1$}t^01gw7j^DOUD(Z^5fksI~sk>>biID>}#i6-P$|&Za^PL zqjWZr^YF5zFb{b6Pju*6r=i z(y|8I3y&4f>Up&A4$8P=bE=m<5d5^wcURd(3W5@R!Q_sFwHuY4Tr0mGKrlpUS@k@u!;Uf%Z^Xmr*u#YcSeMtR}vaX~xOtK;2M z3P$$}$n)M>_;PF8CFji6dij|&FYxs@@IUDjH9r5;=Zp8DBGy0{x&bxlj(lP&s)71J$ZdhB?qzs<(*f*!b{CH7wo6iYlqbAJ$ z*78%9di9a<_vYF)dFY#?CP^t>_VE59ceYc_ z4qRSs9gEWvs+*4eK6T9Dw3UyX z2IjdvnR@Wv$eK)p)fyGjCj%VL?Fo-|?R(09`!mi70aWnMS_Q)zIxZQ7F&c4?!UpY&_4(JEzsdDoDFL$C7Z zraoAcvhT>Wl+u!pXQmEyoDy}nq=D7r2alhq-|f@hq{{I|boTT8N2XY58^i1C^ zRUZqxhR&E|H|Re4iKmkyqq{6H;eV1~tL!wFGMdk;S% ziO#P~{ZVqf-F(yL-*5U(5Ubb=k4kc429%^ADeisDFIIjkI^~XFYo$wOHl6t#Rb=vY1@Q z*Eh>ES5F+fBl_H|-b{f?>&;ciTKA5qyrlbjrP&9mgemIb;e*5p zx(B*!v@_W=?976CCpUYwy%=SluV;eqdR# zVQH?pk<<4TS>_);*4)ZY9JYM*v$;Fw^tSi@R_T@2#i_<~t6BcL6JLj4P2FndJ?G0x zv*+>w2d&(FS8ZGMBKw5{uT=lS$$O0rtd6yEY<(e4t!3-zh={+v~$VB0N3dqkGAgp;J%LGkn5Fmb`M!{ zFDoOb(4U2^os?zr$&C#+59q8NlQ3s zX+4)g>OLF%4~t6rXgsod$un}xnj7da+iaDldUf;Lf{-2#ht=8&!-K}`9=osp;Z0iC z>&C3+FYg!XHp+OJ=c{JY&0|~KveI$yt2t@^YKOW39U@1LIk3E>*W$Os-KGw1wAiPs z*K?19TCT?e*X7o|Arkr~eDt{9Ik8^chO2e2ZarZTFlgoa%E?oDZ)@vPXt7Rfg?!Br zk144c$2Zyca&EIbOmg+4Mg!etnp*8$8(--hx!hUD)xTq>k@Cgu%6FEW?htxiudnNo z!+{|i3n%ZYn|)qe+$ne6{;=u|V~wu`c?CB9zO35H&2ry^jDRLps>{u;-yc>q?1tXD z-iLPV9sf)?Xmi4L>%wM3yT)Ib^m6ibLDK6(D;J$@JT|%6w$&5+ud_d}=(a(l=Uq;> zX!$UD=R~ikHjRyX>JDsk?!0sN{T((OylE!vCf#-N;;7DQ``X+L4$IX%nsY(mU^P@D z>UiHC^}0V(8?M>vlC-dCo1McN92N+j8#bAD)2{y-mkW>j*Y986Z0(@eMp5HLtBSw2 z*!@;E*nZK!Eow*l@=a_;ThyKmn9)#|dPgGL%IF5Pp`Wr0Q4lY6HuX}L%3PEuljxzKUw1Ht|Q z(;BQJe9-qU5@luC%)3@N&b2sBYU^%DSv7Zusbua3jB!Ve^}%hKD<= zR!Q#8YT{`n@LK-9#TKIxuF392I{8ypZD^_=)=FcUTg99uhYn`hpKk7`J8hi5@3Fk@ z4zks;jn}Dv@VVOUvIBqjy2Y(Vju?FH+NGPrrHiMpoHn-q6`!RK4!^TqAl%@VfCA64cMA-G6MbFM}7aA6ER$cA(dE-Q58#{Zq{i8fWVTm=`-a zT_4f0*ZQT$0*||@K3u5P-S@@HrE}CrS1fJ3aPGnZgJtiuTsFv7X{XCp`3(?s-t{O_ z`?f{oi^vlyP4bNUtmta!^df!fy#{qYx8VfpPbdUhkF~DrCylp^05}aIj+{cvmO29&rNts z*R(pls4BrKrEaI;j(Us3*Qw{(jB?ao5!*6BWp2K-km& zoji@_?=)?(&cCEA;zGRKu<1dcf%3MGoBJn93e7V3S(RgpE)Q+DVO4Pt>uWltogWv^ zaBkwfNv8ee;)@FF^vamH){$P_9J)s4@|MbMpZAW5H8HridJj)~=cdTP%@zljMScpg zj9D|nDkWH>*t5$1X?m#Fsyi(bI;_!Fi;0!i-7z*?XaI>xR zJqxSlQQNL*ta6;8d#v;Db(g1?TZLy%)70<$IK5l{wQpw)Hp*(#$Y5P>)#AQh-IgsG z+pTBo`$a?J57u84ep|hCxqGZfj{DUS_3M=#wu$UKrMLU;h&lmlnEzXS|2Fx zzwuOf$JWkM8lIb2*E2WF`fRUiEBm*Z;khkyE(s%!+53h~Zl0Pej7i7~9d5MBFUDx~ z=hJ3V-AnaMS1g#DKGe5T_gvoaqT6GRZp{3+?Nnsp3oG}S_cyKP$YVyig$d_ODz3Nh zN65DGVi&uCdo#v{KPsA?Gq6?r620Ny7q)yiYoSk9MVZrzak6`D#Ph z#pxe)H~VYlY*-UE^K-pY-6ty>Ia|*;u6?>lYiuL^GZ|<7g#pH`!Ul!7H(6)!Ms2sj zW6MTYr=0CK^kt_@U)A-yOzJ7itTgiKrX|0rw$49M?b)M{o{eK}Y_nFK;X21nuRj0r zXX`4(uV0y?-?-ZOa@@7)``2DCGw*NG=3GUq z%#eV(2E(s!n>4VqfrV$p)~?$+TD!GBcTwYT#FxFYGYQk9`vmP2CGE}b$y@l%gbB8h9^sg z7fsz1Azw1H>j`(^%udJK2rKo*c~zEec{66==Uc~%Rd-moD)Nv`zfkBb?|sF?L%ZAI z^IP}mExO@8L(n8JL%tKKWuy<9YSgJZYo<$4d(My(orvBR;D=O^}k zn6d7~$Cj_2%uO2~xuf%{3B%g&$g*3=d+n`F=Ek1e&U$nda>Cxkk<-RvkgGYY;P`B5cQE79^ zyU5K~l=|}zy)OIE_>$e-4};Ht@J&srN?*O{OU2fa0Rc`yqfd2>9`Ac5zw*=k@4FsW ztnS$Ik@>OcY}@z8bXVS|KY4|I{#JNr z=D;ket>vS8)p7AllX|<=2wiPVMwPuYmus5amfgNSeP)vg8`~!y&2Bd|*!%g(tI!D_ zKfVgvo)YJlH{XBAwV~_3wP`c$Q-oFg=@}0%caJ-ry{MI0lRD)!!z@C7?5rw4ZTb%d zs^$68v)%D;glFMTdu4$-VTQYRfa$MgstNrj(ms9aMV?w}^H0swkGU1^UB(}>e)z+n z$==?PQRCGz?+=-}bxiuGp;ez9+bwPrd_Vo|hsoyVsb43(c=$|Bzt@K)mv7q`K5aI( zG}`J-QuLL@gABa!7@@n6_Y^Lg3zZ}rC7nrSpYvbu5nmoee>94yxhw$=;mwg3L& z(qSui-Og~IGWqCr2dUlxr(n~Ie$5{ur?0bUM zRD`Qq3@+`8X<5GiLAICPhga7x_B#Fkvdx!~3D4)8 zP7hhq)z3NJg5SPVgPY?=bXR-3@@`e?*mp-&&fYlxVnuvz;E21eFYYrx;i;-q@=5Yy z>!(>;4`_OQ@&6nVRWZMPqeXd#*F1N2**9Sx*!Z7afYh)_oz{T4`S{aXrJIUdP(Yxc z&ln$XQ?EdGAJ%x*+ij@QZKoa{`1^m}Ip*pYcxp=v<9C+dsiz}&Iec>qN7EKoaRcIt zyGbmQ&5ah-iGA;ux+9@e;o;(g7O(8rRj9?tZa+1;J}TAQMKn+@T0iw+z3^x=CoiMZ z*DmZyTGe>+@dsVi9SRc```J!wmi2I5*2Io`KSjKJP!L-rcRq35eT&r>eOrlq1}IM)P9EF^nm|rx0Bb{O$+0-$gEiMU0pD9 zWlh}igDYN`yg8Y8b6R+@HcxAC(XHWC1v)Ri@^8N_;a7a#T=ijB)YDa2LvA%QbWp1^ zweOxgr&J%in7TGt805Tqj{eGKQx-XfhQtjFZJ%6xZ*6*fzcyc%e0bkW^me1i+|eKU zYsI9PHGaSQ=!NoG2QPeZb}2HkCj-SOmg+ih?5Jc{=j@MBLmBQ>Y3&W3~dy#p3^ zAKY=?jm>&xW|!t3x%^36bIiCCmTSgan(g}jkBalgSbyu;x+*FQ8vc{w2&jSdzg3(= z8^`-a^E8%LbS<;gJ0dxpv3btgaQlGava-AeEm8P?-A55KCVw~js{?`1Oob0MCPqiBL z@2fpqef@XuiAOeGt!i{-dNZT7D>p1%G*mZ!z-7mb;>}AJw9hZ!siCnv+~9=`zxn3F zd%NEBIN{~JwVBDNOi|>#VOj6Z^LN`AU$BzpT)mJN>!|rY@?cQ<>)l!{BNm=6v}qhO z==Ns=-4Y|7SU>X6&XD)1nI`pgI}RL@zECU0XL?lX{TY`9Nn^LW=g;sz;IpJ>e&*9P z!`i#uPJ3?f@n*oHin)tBT+?}+tZ8^Mbd}r7fvx9ui-{^5bNYR5qXA9bWKEAm z&g!PJS$$=~Q!}p@8~w~b9J+V>gH9dmzJaOnqpQAU-~C?tU_5WU+12eM)1?Rxs4cZZ7(W$akK??O-8i9>G9e)(aV zM`5?&w*I_BU6=c(No!;PLE7_fvvLo@jAI?TL@clII^y-uy-YI_h`)u0G8TRm_ zj^Sawj#l4F+H^d9X8X%eV*=b#D-5rsA5B`8Yji@_cwkU;_si#`6I$2FZ60^8`S2%( z1C8pMSZ6dl?3?G~bhwMCXVJqB(;hfaigX*_;6vgF{(%zf7@-8%FC0^1bOlIhmxH)t$D&T+Se%{SQv&zp{+I>usk!;>vE)a?Jt;l1JC2oS z=-fAKx=?k<6#daox@8e#)~!t}h`Lqd^5Dv@!Q&+QUHR)CZ}BYIm}7b_nqRGIF~zgD z_4~T+6Zh#P76iR`y{TtOx26vF`$pziEj!e&?d#PYbCyk)gm<$F)b%yk+{5QZ;$!Ea z7o}0YuObe&^F4a1%@HG?w@Y1w*QWG0U2(=Md-nUL?y-rsURKLK@rQYa)lY8N$*R1| zv6uF8KWm#^Pg)+>5H1Pk?*IP;*<~B4oYwnr2IB)(){@B@V zw?!lemc*WSn^e!?TEW^YT{Sw2>svpL4h)%;)@Ib3i7yR)Y#!zrqdw1h0B=y;Vgt3X zk_~5@4j%WsOUuu~{D?W47GvLT${A3RwSLg0V!q|5rgy%Vb+yWmYTaEeZ1A9vp7$(m zhP_)Rif-HG@`sGK-M6(k@7lt-=x#*b=_5NYENZLvd8D1uNcSgJ)e{nmn(ftTvT66| z(;jmd%iWtr1*IEpSY*`yNzom>l`&=JFK_MG-RN;x|Gq9~OXc%EJ-L_mkIpbT++%1a zf{Os7{~1Mv|CKXb!!ntr6Ea_%lfB-xVa;BRhr0QFlL|Tv9ITe4;T^Qs&+?R8=%Nlw z&eilwd-YxWY}JL$?H%hK?ccb`m3L34OnqAX{oC<+8#8WBG;1yCqgI_%9>2;jchYeA zu-I2RoBKx%c|F-vtxKD;dd~|k^V?l<+a_AQ=S0Zl(58cSOWl+2roK)sHrncb?&9h< z^G9@;ow%r9*O@zR+*@|4U+JhVc8k|Lf2`7QzS8@`BwLGyx=%C1)I2rslvda!-Mm+I zbb58Of3k|h-km-bf;%I@o&Uiq{wo%3Ta!K{gREjvg?&kkZQRIHLvQc%O*%H(KYg0S zHown$qrt`zU&{A~j9neG)NkpQ3eULj%eEh%S+=)2wrQ#P?Cx$>miBK#_ayc?Ii}1Y)$PsN zxXv%jqPOpKdH>Bf?S+MNv9?cTNm|s3WE<6nrlpmwr;N|FjN5qB#i8aOGZ)Q9wilxC z@2<|V`X}WQ{Wr?>#@Qzz|8&bAkGK427+JMz!mxWOooDTSbU*6$?5SP1+@D!?N@q>| zo+88a1v`D7=gq8h&T&p1qt3RyQlpct?^vwJT%1`xsc}K{gbCvtnzz=qFY!@bf1*;Q z<&DXjA7dno?eAZ_`!w&ApN+q(z^;XzS^3;|6U)O_v^4wXCTyqI_Q3p-u*Ytjyj90} zEHyDRnje}{pq6#Cxbe5%=N=EsX*>FL^w+PGEyiCTy(#el_dfpP>B#Tq z3BkRL=AZ1m@Jg)doxHql6Z^IppgXJYDDTToTJI(bc4U-XG8mu47JUv>#;((KDm=e{vE?T zFGJ>7%vgS2Q!`|Pj-|u1B8!MQ>h}Wr4&E6vzDas)%O1i=jT%IwZZVE^-Jum zFZXXe$9r9I*6_*xSrf|_Ewi4RXykvW&*AumT}$1L$BgUlt+Q0_QuKUMf}!IKi%#t> z?w*w{nxSR7x&Qp-GL=xri!LrMIW8Wak(PsnUG~f`c+!8~rpB+ct1jh8ht~`CQ~flx z@BXZZy2BD&o0pbmO*&n-q3+iAl~b+ue|ePDRcm6B*2Lomnl4)|KA$1(nBzHFk`Z6| z_2y=+^;a@f$4}mF(l%&rR)crPm)z3!8?ic1+aj~|vdccZHM~{jO4L`&12a4MtVs-iu>5%y?`_ZTeUIq(GUuP$GMm|J&VM9;o@&{NoP#r8XA+Rx2!4hfskPoA-5edjF~ zdjy}|)y(K!$i->zJdNB6o4#^-79`x z_O@G(ZyunkKG}HY;uW8FO=}U8AKZW9tlJ|3)(yRE)U~PYFhkAf=iSwtoXKtqaNcL zJv)t2~#C95|QRhO;5|?&0YL8Bx zzA6qVxVdNl?9Hq1kKEmUc$l~D<%5=s9$dba_+YQj#^lT&Z6`b29oQn&z5PdY zGo9i8bWcy{(4YSIKSCV6Onf3C#@HrYn8KRd2()1 zzm5}&Cz+h;A87H$87feaKsahFs+yA|wpL=>g;}Or+=p9>jS#VCPUc#1xWe+^= zEIyyKQZKnn&qZ#NLV|*JWMzN2;_7@?uKLU(i!YkL#_mK$ocT~gS)kL}HW5Q!zpIQr z-ax}P=kVtV*R)z}NtbN8Tr}LGx$&!`tv$3mt+~C}Xs>x}mx?=fHhP15+UK3+o6Oy@ zxJ5gyHwT}rTVY~ZU_F2J^E2K}GG8@6Gfh3(>E&amX}S#z+dr@LI@!SPS#Ye}V8b%= zq=y@;bYpc&_dGtZd(H}z=aXifUJ-XCJh$7DO`hXN)STEJX?$j_*1&h$7bRWP5Cy*7 zT(`WT{}!ugaj{$6m4S7S&fI!S#WFhYS?EvFE4fa8Z@aB_(#n=r154WO-N$#i z@l_h0|Mhdv2a8@xz7=(uqI$G^c9$nLk;{9Ap4^bTC-<%6he6);WIaDH;1o! z)?kff)zqLj2ODghtv>465zS3&@5eX$q`N9(pyBtWpBHKM)P9-fVApV}`Y}s&^OJt& z!rh`Kb!=C**^u5Ma;hv{mlxwugdh zN@^+=)s#MI(9vbV<)qaEj5N~MW{mb;W&B)RU3f5R*PSB|mu@|5Wn?<;`{Lmfjvb$Q zf1>OAZuyZ1>#Xa3R;yc!H!~_?MS<5}Id47gZ|Zy4y02i|*S!e`9vreCW_4qKx4j?E ztbg}?Yw*ZZ{sQT}ZcYKdkrBpm1#;7X2$db-+s@7|ThnrgzUsQo4HpV~txz={viOl_ z_?U*LmddqC{PraoHrw_{)ud6F%dm{;o4?AxzcRh=U!*tBs(a{jlZPf|dtLPIOgnG5 z!hP1I=E-~XqT~@yO$Mw#ai!b(2J^GsQ_cF!T|Y!{*`TMFzW?Kv!sFdWMUU0?UUBD1 z!i&y95yz}9?us!SRXC~BfQb`$RdvTM*z{nES;v(JcD`3r%^Ng*WNVAew?jtfE}hh9 z?d*3O$7$#s?O4-o?%E3heT`qf>d>g`@u0kAJ>Os0Q0AAVBXBrpG^&35;Qrd`PyD^S zPTL<{^6bd#DSfTRmN%@|>}{OQ`jZ+_%PM6#jRtug5xdwICaP^~ChXn*xkKT!yA5~s zQu)~F-qm^UbC*51F}dc#>5=C~JsdFYuw4C9j8%9<>L|Ide#dD;_xAiD&i2~i|IQ|K z%FxgIO8L8X4|!)8dwu<~gC#pIb!fJ`{mS!c&pvi&b#R4d%$e|R6)6qAXvOeXwd%B3 zxS(a&-2bD!ua4?tS=PnfCAbFn0Kwf|65QS0odCfdg1ZEFcY?cna1HJbf%i-HJ~s(x z-+R~l^R0K9)ydG)^>xiq)6-J*&1&$;1ECxU`*Pq3g#tO$Bqoe!zw$(FxC4|$my#%0 zO0&X(YOSSwC;b?)n_8-+yW(~~nZ&aHU0XV44^933xmQBw1<-qF_oK+5uz2%@yX3tB zZR)ofgEDe7Y3B}$+fn%T?=?~*E1Fg9^fyoY6*%6Yr**N?!-(c}4z|S&y}O(PbvUk> z#!!zQL>NEU-?MqYF7S5qEiU}j?lcT9+pB9JjK~l7r^QEChDZ#tgV~!9A&`|PtxK8I zvzd-a*6L8ViN^^JVJ06Orh3)hvBRgbZ0Qm}ZLIWOd6Sz6P=bBqNR3Z1OwO618f!D^ zIDn@q-Yd@XO-WB4R4^@KKizok$?8jhQ9+CcO_>oS?b1`;%VAPA*#7mvHx1t>ZpKmj z@Db{8gA?o^B<>iW#XO;@o~m*7d&iXimumel7y4g9dDcW#?XOpMQA$^7o*LC0hc4=GP*FT3=>^#C@(wq+@)8DEo^!-)3HEjCo#@M;+_3C9o_! zMThMBza14LfX#^jxzPbnA6rJ&twFS7T@~fq%8(bu!LDh8tUt7DsrTe`waI5j2o&__ zk9P$lCUu@8;GuthO4vyEm#FitDJ79{rMA_op&mA=>?D@O^#6qMZqhiGone;^wSh)8 z;sTCwSoPf+i#g?IMw_hr2dAbS`Lf0xP8FKXE~50*)6gv~NhZ7Y3{r{nX!Y_AEr?JR zrk3Q#kM*K-Z6B!!T*pAXdQ5TeHVp#(XQ4s5CaYdE=WWTNaI0*~7;$mu@@SwJ-|u|8$L7Rh22<0kLI3UP9G)(@X%vhE-0yvf_HdQx*nF>+|5 z`yju&60W`E@VK`*#_KsR)9HA+Mpm%_rT1~q0Eg3gQ!%~=H9|5i4rhMv;fs9?MR@#z zJ2^~)N#iAM!lKB4FRIIzO<7wMcB^E8)J8n3LxF!oY#E8_DW7Mjbof} z<5)sF4l>1{&}e+82;g#oD4c?nxciFmV%Z?Rtwlrw4r1`E`Qt>;jwEpi;w7(59mHza z(WW6?(3**8G(^O8j8Dza`@x;gYa&+p*XBoCAjfX1FoFiC!NZRHWzo2OOC>(?X*h_; zH4>^dv^7x3WjCL3K$E*ZI2*c8UqPE@5DdO5^ZshMzU?sTIr_*<{go_#K7yH**ch)t zH=wkav<&eG(|>O;bZ8f8JiDV)5xB~f=?mFusM{iD>aJOzKnlOfJaGEt_tfSu5o^o-g;MVUfg*2i(SQWt`D~X^30{E*#OvFPjy5AlIA^P) zvwcMWxOoHzD--4um{tfxGn+;TR=Eqwk-39j;t)-%w7dor!8ScRdKi}$FC&PQKn~#x zB40!p1H2V;A2Yrj8z!paEz`kEDUK8PLqQzVDpxM7dd^HVB?R;y7B=O+5 zb1)d8OM;j>@LeC1^x6;69cx+M4~z`5nJU8TP=SSaca3kR#Dd%T!ST7!FuKwxsX*Vs zhNx0g$kJ)_!5-pb*F8cI7z!`YuX(yYvF5WKG!KQPMm&qz1ZGm6>&ruGfqvCT5seZf z+yf~ZJ2G=4Vgv>m3|+5}!{gDTq@(iC!rQdJJ-=0Xb64!~0pA02+GE%uiS?4Q4cO=o z$pzp|tKi|qgVa~!kvQ#PQly*AL0>r>NJl~IkU4o#O?0i(m2ISNCFMFaon#~!-TjJ1 zZtrd(YIkA$Tu!hfKzDaX3jJ&?M}JfI=)uZr8I;k>+c&VkPda}*&z-!5L2%Q;R4{Z$2h%@By%Dm$ae}e|okkjnFTHBmvb_%pb}74G zYKRtpPGKoIp0Yn65tL!TvRo7!cDRT;v1bgiDTqcqhSKcL;e)u5J2$?nj?zJg1pyGX>U{^ge!0h9g^(wRol+Ht%#wI%LV#z(3CJ1bn)XM?4RyT6V1$OxFXc1ahNZ=UEVh}n)T=DN=!{Qk($Rj+RML1 zQ%#u?>%66oNfRD`OUS(`M6{+4?W|^R^F_TUodH>h?#Z^G30E^3cjxS@i1*5hVl4TL zXujuw&sy?%%5|wCp6BoXWxm<6lB5)R36iR}kQ`(>lAl2@O5Z3=7q=)GTnr~BT@=1; zmC(`A1#YbYD?j5HyAY~_dk~YIZfl(BuXY`8b397H34*KPe^9EW7sPiK{F*=+sW1P) zxNU1;svW>}98g35{%7$%>rN3#XXd*{v7(0n-|q&tivhk|)S9HQx&lC06!+hH=+4VASWsZHpe?0x(T&r`7lAKOGM z6XD0nrs_p?u`MLY&5Cm?lgs;)0ljc-gb3|m>Y*l}XR&NUS)l}xF1y4owzqPR#bttW zglAQRLt>veX9({@uX=ai69{wWJIllmoflsMzUW7i9MX}c1)>0qqQX-HAEn4}wO#C?gpZT4pHl+H zSy}&Uh4_Z4#ZT*&T_nZ2y-eUHDMG?pI|uuaOIRz!F#bJ#ukROGxPgXsfl$G9kh8`Z z!cZe@!$=KNNdnes%u-3>ZI-ZK19!V4bsJE9<@Z9eg)R;&b^-N6#l=HisDNSBZ8Rk= zdylyxC13y%zcV3YW@~RNo_{OL9eSr#Oyjxsl>VQS=*ZTdHZhU5LfZw+Oe>Th`3$&VyJbK^yzo) zG$TX1k7Jn?B{D-K)9Fi5+BL@6RWS$|q_%brQpYM!{<2;$m0!u3Yz>6f#@XTlPM~Z7 zRmQv-1GHIzJQ~r0te7|3@+w@-9HhQ|E&7M~)61IQF`A1$o>K+ue-g(qN+A*PwvfBd5;|MIEl-#> zxb>k}HNL&sv*W*$7`olC|L8>0xn8X*R&x^pmAkd?Y1hDx{+t1XW1qIS#Y#b<%Y#`@ zl&%gX9VAD2CF>`EP~&DkmA==-KZcLsb_@gm+5Aj}RgGshTn7I@1Z{`N`jf$%h?YyY zD;A&}JvIme&`nHSm@xfQe1?b)#V>3SvOt@daCl+*75abzDPT55{G<{&#%oHpdE}+1 z{*!=3g6n0Q2mLl2EAKDPT}P28Rt?|Rm*DGp-I~|0Ao;Xy(a2^F|^ZugfXcO)32erE=L$Du-A{{Rx_ zhZ9jHOEKb10kw&_=?X?8S4$6vZCkkqQY!CL1$D=4;hkw3lSQ~_ZOt{mL)UT@;9oUg zeGitMkST}b&`{3OEslaH(h8qLOt4bJb@_^5g=E`C6FF+lA|tOKb(WKtOr$bk4g$@{ z#gtDsK>5dkE}`v?q%iJCY#v)OxxX#5tegJzo}@LPUDZqvO05g(< zM9EYpoe8e(LeVb8-=&{`=gaBB=$fa>$9mz0MKFUMWkl*56t|3Ot~SIn&oI5WG38R} z*gY^e90S)~=wJCRwq@R?lLr=^aWyK0vn~~`Y;-5)8k9dOFepEyNF3y)3FtN%iyzoL z!xXQxI5OM~B1|-3v6MD6m&Bob%om_KW%GT z%>qf#B7l6Yxnr8Vnl(?bp{TeuJZnInA;lp?SJy19LRJG}2IUC9rnk(0%CZD+b@{+7 z#mKv)!L=A5j(s}>tEJiQiK_99^BrBx01EF1V^_s(b?ZbAkrQq2%oOhj(=|#uqiT2A zR_-h25y1|n;6R^`MBA0*B&l3vsvCx>xh~JbH>|EI-CpA>7(u1o1ySVP#Wv0Qg&!eT zJ1b8RL~#+>5qciDX;2v3H-`$#T0e+Sd2c`M$fCg`I-Z<=8G;+5e+U_UJqZ>s@!*G( z4#T__kPrMxnu`nR;c9#VV(E00WzECUDK5Y;36sE|WAkpm?90XS>N+W2-_TTx%E^0g z7_<*7%oISag$%McizSx}`XS#cyU@m*y}6U&dM+4hMb5nFV&-wM(1tNk0T~{UA<>4Z zfxDEyis^JEECT+STVi04rThT4Gc?+Q@SL7LE2h(S80LFd#RLN=qDZ9YOi70{Y3&uS zXeSc!y)?8($!+%`D;@{*l)APj(C5U$^;v=9ask(E{c~G*QC#|=McjNbN#CaRt47v5 zq8}lf&Ssf?x2rYrjkvp*N9<+o%>#i0C0OP%uYxC>2_ab5J+VbhV)Nfn!Fe(cZMfZ; zVTgE8wcR0&(Oi)=E-Q4*`Y(8rCqpD9LhYaR@ z96^ahB{{)Zog5{AgN;ymTr`tmidp17I9)uqVWc(8++J4|xOdySZ^Yu9gZZgKB;l|b z>QabGAYGZj=H(TkDsm7opGS}gMHRc#a217tIg4O`ofObP4hziG!0iX`Z0WVG_9l_>*msEs`%Awkj`4jef zKchLP=$t`Raa$9hj;*Z%I{0D1kA{AzT7>jpt>7vmJKOznax%~h6jw1WkiCfNWTSv8 z|~#({mlCOV5;i0Mp9*IfoA!0T{YJ; zriu1$ic{e}O`~{hstgtmzQvH3gFMLY#3p_5@r{YWrOzV9Sv~UZdr5E)BkIE8fZ?VZ zI!UVSg%ZiLs@xd*(WZ?&nnF08>M|Q~ZV4LL4$xrYC3HfP^-BI0>QRY}qLAk7Vv+*0 z2I@Qs-y+{;fe$2Xh+-<1l4G=2H3iBpR8X2b6ht&5P?|=CcBCMM`fqXK% zS+fwa$yW2R%~$ic%LC*bHN3%0g<>eVB1JHY8Bj=r*I}G9RG*jt8E3}K4)U{_@DDh? zLM7zX0wtZLLZz%!Kwc_P+E-`z35gCYQ5&)*R12~xQwy^#RSPpC{5I{`L=Ik01tpMF zH-^xHFCI*{LlwoLAfCju&rDpvrX*gW4D4HlnK;=-Y|EX85l&KM(-rlk(g)d@P*}KV&!x0~95VigagUA^=#qIRJ^h0zi=npy(a>zuGX! z`&T)~zp5Ji7x+&I>q@Z=UDUi{OBDs5cFhGX|Lr`OKx^PzB5Jv${0uqV+oOFPIr_*G zY=V!(^OsC6xA!myVTYu2tvqPeX^QNGt*4F}-VMP}vGHJ!`bv)smJHq+lHT+Z8t*U6 zMRFEQY@IqrJo?(E6|0;%&)%{cG*b(;#R>C*qL)!CtTk7EKa4O3e)6Y(A}#H$ulC~E zObCfIoKU*CtUku_mg82sceY1dZ%388s9P<#Jh)M#FhNEO;;om ze)_n(Kog(v(ZpFVYdJ5{1g$MKf*iFBJEzm%I0>CcPXB#QZv*Y^#Qbz%Z*S;bPmimh zJ^}LA)~=DvzJ3BWBO;@~q61d0j7a^W!NSiJRCC_v^pDk>vEEx-;JhNEtZnymQCuEZ zk+?j??e}vwpU*5;Dbs4RAord+Rs!*`3g+37 zHhe9}Gn-&5HSl+4!Np8CZ5swmPy)XDu#TnU5QP#v%RF>Tk+8AE4VTfPT~xc-%a zwTm6?5q+3%A=jKTg@uh1Yh}(4j;FH_IMLH&zVP-qk~CDTRSZeu@_*CUDifQNGcTrh zbce2=Iknihfqy{t7%i!nspl~omL$_cIzo@yc z&G|7AT<~6LL}1@jJ>hU#u6j+#l{M52$s9r50626Kl|@)mc8`NN(mB8>#ByWUV@Pm8 zRZB-%r)4TDL`#+jrzhnRL%d>up6yfiee4+x`GPY095GIRs=)#OsJI%Ok4z`@=#bu} zS>o-9)rJLg*^D)SYOPZ;FeeJJIZBqTjjqyhRmn9^1?&4IY$M67vgbs*YW}e4-J=Bu z@3#xD_^FO9`q{a_bOf@(K3(2YC-1$f$gaJ%#9~`29>J;PO-(wbgpqretU{tQ$nzas zdLH5`qP=fs1bJLU(GD9vR`-T&ReqLEK7)>u;L>=VrNHEoSM=ovlEP_<88cx_LfThI zjO@thtqt##_C5Gfm*MLOmlE#H!eb8V2FXm4?wtzUYw2OQXiy9b5eaU^lc6sTC!I(p zFk`i{^aSEEu2P&yH&jKNjUNpduA@Don=xy16eBK~wKPQCG>8!q*-LFEvC{DJEJx8qp2gSuNbH zSa2l{8_c8`LyGhMVN}z_=!K?fR3`5g>e#Lrt~O%X(w1pO_j_<1IwGqlsE9%p9ErxDld;tI6R6cpLKdD7BS zYe9$9R=vo}tHUoGlpXD(^7Gw6x!A(ABYMB-71NANPkHb|vU6|_KIwT{LD;Aq6Xz$@ zIaYIANpB#g^`!IY7e^{8&z7#ua#r^^G^p%ZDrIot$zVOvwsXQ(-%{NbTcNbKHh@s~ zdGth4UadTK8LLc;!ZB8_3gO%f%PlD&E$z_E27T}7?dVOXV_+5O zYKIyxEm@+M0`Z}v5G^pPO+^g0NE#{gIait$%KOs^N%mo`MwykhHO?nIbPIHtOmbPC zDm-ii<^JN9+aVDm#5Yvj<3N*c0>y8IqOuuSQ^8?TBGP}%Q2>PZT2!uV&jIM z4rSuf^(L40NhL|J(>(4etHIx|qVAkPU#wE;J_#TniIWX!8};9&Q+U4(wxgf(PGQD+ z^~!^M0MGvVikWl7XOC0?*zQR~yDQej37yHxhS*Z&oHZ!|BUyOid+_BAbE73UBC2R> zHi&Y_O(tXDc>d*hi<;IaIzb&4Z7bK*+DC*g2GRgO}mCncD9#9Qzd zny(AIs5bQ2wbIX-Dxf6FRk)`Wx_zIf9?IDIp^#pKa_x4WtfTm^JKI7Hou;t$n9eN% zfwEq*Xh!4mu;;*k{o)r>bPVnd7L9&lR?ygwXDFPHWU4<>5M-Wn5Q4roOj zWB^1tZP6+{su%KKc^fmKb6l6UTX#x49)=YdPVAPd-(3vNZ0|MqxQEoL$O4%hId8U$`)hd(Hr3G zCuoFq6_)V|L$TW$htEY@S=v>($?1_YVPi{<1{~;$2+p=JC7`=5?NK-9rqeb1FCQ#CcxQ)u{j?!nB! zGo;^s6AYNUDO+u04-esLUZom9V#f%I4I3AA3AAQRPL1g1qzz~T(tb?6Zog%(^*oCm zOXiKk;Kg~ZbJA7b6`Nc*-z3zK7e1q6(qvyBUYNhkGE6UhURj)F>1us6DrzX}VLqYj z{uVgcfKxdoDAkoXrd`-lJhuh;I)P;JL{X{);X>Y_vyGS zzNPA@OS)y0`)ttpv(2XBiqGTT>HYju(JJE!MVby*KPxUjbic+Re? zvOG?327*=xGoAnoum8R>t6%V)1{n~a;tBAaMf=yvth1rs``;K&4&#R+775V;ZsP6{ z;%`v<=DKG|UJK_GQYk9VB=GvPolXT5*J046lEAzWWkMe}m}W z(Tb;-?4xnJZm{pJphqZW_3EiSA-}ot#MF<3AD2Y)782lhy!}n~MtWlWis^0d=ydvL zF-@qJQ)>NvVkqXa0zNm8w{cDtV5Cu%nzhhi@5&eN5&R0Gb+BWsDTi4*rUA-OzP!*nk(1t!|AyFoCeNB zm!Ju?vL~!+$@)%igHHx2`}BJl1?92S3=rJ}uIhau6c|a>B46txtU2#TM(p{+8IJVf za1Vz-Ea4rXcnU01?L6ft*h;a>Ji?gS=$l~akDzi);HgssAGKzJuShXQ)qS)z!tdEcSE1%bvo7X_>X?;0#%}n z3IG#gh5pU!z{uLlK|xo~!tjUJ!F1dzAaV(*Zog*PMcCh|Mqqy%a3Aw+AvAT}%PRPOCFoNn5Zu zPDZkn0`$w<(Cl^S&TVK^QtqfV1;9)XYhCYa7%og#&bVr}3#e28@CLq)#DW*5{t6>V z*rA;ZM;H^gt;^T-M=eG?J~eoO?oECCYrQT`8Zqf)#4fxWii*nas8l{Lnc)!K?p#3& zm=d|m+9883p_J4Qkh(W=<2?zb3dt5QB)W=jDk|xqi{48F?zUu$ob7R+-&S>E7kn)` z(kRywBSyOPK*9kGW5jboUagNutnGI}r^}mAyS5xyRn&`w&NQL1zi-6H1 zh?Xn)_8Vnx1?-4EN0?a6jl%)2Zt#JpAh92yd|%g%w+l2RWuxn%H}|!(u~A!8i~sAJ_Z+SwffSHHs)+~d;`)8?@$?e zW$)9rL=iF&6tID{{?AxNdVOoVpC(9{4F*h+2?VI}|947_QI`dTO8Ku+U-7DVS%B0F3Z@*yyQ^iRY=v0b!FUxE%`IKD;^V)CUj(sy%kyP>GE`Kn4HZJnt+GPoMjxHaWI#O zF2D6{F)71VN$+M{ikNrBbyz3lH=_B*BkbFTT3#dPjMO1{w_50Zis`%v4|u!U@FP|} zh@8jpb;x*a8vpz%787`d|HFot0DURa?>4M!WAj(^%70Aw$HSZYPWp!lw<>S&3N=>C z=r~iF%tFy_2%3RULa=2=bBoiUELM$#e7{MHipkqANvHLo26w9l~+P8W^Lso;$X>1xxK(Ui>zKg>J@r{y1@QpqU4y$WQOgj>BQM_9i zES-AUG~Hz)8+^Epqa~Uq=haHA^hPeltS=9Ws`UN5?8m!)mrB`Z$)Ysu%bsuhYe}*K z!mTF^(Jk#GWtZuYRX95?Gr$LzYIG7v?_m|_bd{6Rw}51!x%33(%lN{8TN*NRxHx$s z-vm@xzR8rZ$k!-SGA1+v(r{hH@()gn=M9IUYvM3O%s8+10qSl6clnH0c%w7PPscy} z{#KWUnr$1WH>eie0_2`$unH4RhVl^2sm74gAY79K$s1_CX(h){;Ek}Idxp7!tR8C-6;yp&YS zFu*fpr`i~_Jnn_h|Jebt@Do&q44@141B^9Me>dU3;^qHi#lK!Y(+Sd%i!1|*GnYlguh1!Wk!BP-mx2W4(Q&U)- zXTvGwipxnGOK#!Sgyw_SRm0VpQSAHEzAy1~tW&4;>_a>D0V+bTOke4KJjrI3iG_mx z*pD|5zjw58!!VEUO{Ob1Fqxk|6L*2#mwD4Gy4-AA`F+00C;*|qkB=fnfAn|~d^-_^ zzsE9$#2)3M2iNeDPB?ZV!=5Z>bf*0h!})2&w+v^h&2LAg=i3v+#OJhQ&)V`(?nE8% zDlUxPu%MLU61pMHIP*P^Sn#$4UyIkpZ@~~b6}OIhZ@zwO%s@hw5c?z|W{N5wK!g%N z7G0B^FG8^fhZqC<@Sve|e(F{`oW-z9Q<{*0bb>NTSs$VjhXgv!NBjxxl@RggAk?0< zA=lcB4KzJzJshkN7opEF(Q7e6wgL>jUlfbF36En(zuPJA5 z;G|DES@$!ZPC7S*0_^#_u{=u9=nJWnO?~W5&3{TlMWwD_0VbuBi%19cnj6fWN?FXt zbYp63yl*meu@|OjZFKvVTkFG<5m|^VC664P=#w#KOCmRz{c*k9j0UUTAQ9$T$o-;n zfACa#1MY^4GY#5zGi#eBE*gdkuxb@-)pTH)v=Iu5FlRrPKv~3JU zDfd@BtM^iDiS}fOo1cxps#lEPEK4A4P_6d}gw9+AHN0nKXKCh)nJbV>z8Qy#D zD)43vylb_nFa)~E{A$X3`5&!7Bc7c2f3t$0Gz0&!f?uyCps=xykWL09h_wgb6Qmuh z_uN4=(ycVUtkHBX@Ow4ziv|KlXrAtV`RH6OsaQ`Z#>vaH!}}pg+`^#6MG*cI@+X@n zYxRrt^_eObheuM#@JrFUb98OVsG~{}LF5@K=%iIX;v}BtBUYU{joBY7H6R8%B(N?D zVjaDmgj&lE!bhLsO|+#Pcj31L+d*cQQN8r07+eB*x9EU#S_;RHI|bHpcl7fn3pi3l z(Ll8rxReiy5;GS?+8^h509WzvGeh?akesgnr7!pyR{tL>_#dnQ{grC6n<|(!vF0iE zq$y6_z{g>V`|AMi(zTAw;ZtwksY5GWCFS8mN^BLyw-jL!ZUmyByvtsV;5~Z!*2WlOG??0}7 zxE@dlT;b!nq?TL$(HFq`Ip0bjqc9AH!W_}jppEMC7y+yDBAgmm|NlOe#kfEcjgjr!ZA z{&lD>2w1l?v~ti^aJ4bC*ZeVIk(2xrAW6{+{0UHd9WYG?u-p8WzyP6me+@8x8qrI^ z0%x!EI9ebemw4cxOya*JnE*EXGgb?*Y~^SPX#SttO{IZ%!34DVL-8LxZ@=Y23S)HN`)`$;tRvTcb8za=W6KW@Ws7{A>Gz|H%ah=5MYI8Eo2fq;HwfF{2sD&fC@EDa4zb?Hql zb&U<*(AiiS|E0?>2o}@hQA|ZZ{3Rg&3BjE9?+}c1fKQsdyquTcH2I~5H-Lui=goNe zg_D;MFB$58L0sPc4e>h>;7jqB{DHs3;~;_m(%`>g6MTvECrREf93Y^3DByph%zFv& zC;8m(0GrS+0e(@@y+rwwa_x5%CfJuKKgWMB0sf??`W-+F;U&ONHU1IhPqL%mQTUKv zqWm0yyj0^)KA_(Lw2)r{{8Zx~QU0XG`5h$<X(7)|gycGX)Y4~?>E#cq9|6VA5DgEcF-0#vvV!uiM zTBCaj@aKH`cYp$^-%a43X4fyV{+yNlj#Vb}&se|aXfLt;9NYhnRU`M$SieU4FR}g{ lApVZk{O+HzehnApB*6inr2+yX1pF8P;;%au052z?{{zAnS|I=c diff --git a/docs/IGT DPIA Screening Tool.rtf b/docs/IGT DPIA Screening Tool.rtf deleted file mode 100644 index a6db4ea..0000000 --- a/docs/IGT DPIA Screening Tool.rtf +++ /dev/null @@ -1,990 +0,0 @@ -{\rtf1\ansi\ansicpg1252\cocoartf2821 -\cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fswiss\fcharset0 ArialMT;\f1\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;\red34\green118\blue55;\red255\green255\blue255;\red0\green0\blue0; -\red11\green90\blue178;\red225\green224\blue224;\red99\green99\blue99;} -{\*\expandedcolortbl;;\cssrgb\c15686\c52549\c27843;\cssrgb\c100000\c100000\c100000;\cssrgb\c0\c0\c0; -\cssrgb\c0\c43922\c75294;\cssrgb\c90588\c90196\c90196;\cssrgb\c46275\c46275\c46275;} -\paperw11900\paperh16840\margl851\margr851\margb851\margt2098 -\deftab720 -\pard\pardeftab720\sl259\slmult1\qc\partightenfactor0 - -\f0\fs64 \cf2 \kerning1\expnd-2\expndtw-10 -\up0 \nosupersub \ulnone Data Protection Impact Assessment (DPIA) Screening Tool\ -\pard\pardeftab720\partightenfactor0 - -\fs24 \cf2 \kerning1\expnd-1\expndtw-4 -\ - -\itap1\trowd \taflags1 \trgaph108\trleft-108 \trbrdrt\brdrnil \trbrdrl\brdrnil \trbrdrr\brdrnil -\clvertalt \clcbpat2 \clwWidth4920\clftsWidth3 \clheight260 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx4320 -\clvertalt \clshdrawnil \clwWidth4900\clftsWidth3 \clheight260 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx8640 -\pard\intbl\itap1\pardeftab720\partightenfactor0 -\cf3 \kerning1\expnd-2\expndtw-10 -Project name -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone \cell -\pard\intbl\itap1\pardeftab720\sl259\slmult1\partightenfactor0 - -\f0 \cf4 \kerning1\expnd-2\expndtw-10 -\up0 \nosupersub \ulnone Beacons -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone \cell \row - -\itap1\trowd \taflags1 \trgaph108\trleft-108 \trbrdrl\brdrnil \trbrdrr\brdrnil -\clvertalt \clcbpat2 \clwWidth4920\clftsWidth3 \clheight260 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx4320 -\clvertalt \clshdrawnil \clwWidth4900\clftsWidth3 \clheight260 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx8640 -\pard\intbl\itap1\pardeftab720\partightenfactor0 - -\f0 \cf3 \kerning1\expnd-2\expndtw-10 -\up0 \nosupersub \ulnone Project lead -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone \cell -\pard\intbl\itap1\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 -\cf0 \cell \row - -\itap1\trowd \taflags1 \trgaph108\trleft-108 \trbrdrl\brdrnil \trbrdrr\brdrnil -\clvertalt \clcbpat2 \clwWidth4920\clftsWidth3 \clheight260 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx4320 -\clvertalt \clshdrawnil \clwWidth4900\clftsWidth3 \clheight260 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx8640 -\pard\intbl\itap1\pardeftab720\partightenfactor0 - -\f0 \cf3 \kerning1\expnd-2\expndtw-10 -\up0 \nosupersub \ulnone Directorate / Service -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone \cell -\pard\intbl\itap1\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 -\cf0 \cell \row - -\itap1\trowd \taflags1 \trgaph108\trleft-108 \trbrdrl\brdrnil \trbrdrr\brdrnil -\clvertalt \clcbpat2 \clwWidth4920\clftsWidth3 \clheight820 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx4320 -\clvertalt \clshdrawnil \clwWidth4900\clftsWidth3 \clheight820 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx8640 -\pard\intbl\itap1\pardeftab720\partightenfactor0 - -\f0 \cf3 \kerning1\expnd-2\expndtw-10 -\up0 \nosupersub \ulnone Information Asset Owner(s)\ -\ -{\field{\*\fldinst{HYPERLINK "https://islingtoncouncil.sharepoint.com/sites/IslingtonConnect_InformationGovernance/SitePages/Information-Asset-Owners-&-Leads.aspx"}}{\fldrslt \cf5 \kerning1\expnd-2\expndtw-10 -\ul \ulc5 List of IAOs may be found here}} -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone \cell -\pard\intbl\itap1\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 -\cf0 \cell \row - -\itap1\trowd \taflags1 \trgaph108\trleft-108 \trbrdrl\brdrnil \trbrdrr\brdrnil -\clvertalt \clcbpat2 \clwWidth4920\clftsWidth3 \clheight280 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx4320 -\clvertalt \clshdrawnil \clwWidth4900\clftsWidth3 \clheight280 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx8640 -\pard\intbl\itap1\pardeftab720\partightenfactor0 - -\f0 \cf3 \kerning1\expnd-2\expndtw-10 -\up0 \nosupersub \ulnone Key dates -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone \cell -\pard\intbl\itap1\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 -\cf0 \cell \row - -\itap1\trowd \taflags1 \trgaph108\trleft-108 \trbrdrl\brdrnil \trbrdrt\brdrnil \trbrdrr\brdrnil -\clvertalt \clcbpat2 \clwWidth4920\clftsWidth3 \clheight260 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx4320 -\clvertalt \clshdrawnil \clwWidth4900\clftsWidth3 \clheight260 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx8640 -\pard\intbl\itap1\pardeftab720\partightenfactor0 - -\f0 \cf3 \kerning1\expnd-2\expndtw-10 -\up0 \nosupersub \ulnone Form completed by -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone \cell -\pard\intbl\itap1\pardeftab720\partightenfactor0 - -\f0 \cf4 \kerning1\expnd-2\expndtw-10 -\up0 \nosupersub \ulnone Alexander Rodriguez, Jason Warren -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone -\f0 \cf2 \kerning1\expnd-1\expndtw-4 -\up0 \nosupersub \ulnone \cell \lastrow\row -\pard\pardeftab720\partightenfactor0 -\cf2 \ - -\itap1\trowd \taflags1 \trgaph108\trleft-108 \trbrdrt\brdrnil \trbrdrl\brdrnil \trbrdrr\brdrnil -\clvertalt \clcbpat2 \clwWidth10000\clftsWidth3 \clheight260 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx8640 -\pard\intbl\itap1\pardeftab720\partightenfactor0 -\cf3 \kerning1\expnd-2\expndtw-10 -Describe the purpose of the project -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone \cell \row - -\itap1\trowd \taflags1 \trgaph108\trleft-108 \trbrdrl\brdrnil \trbrdrr\brdrnil -\clvertalt \clshdrawnil \clwWidth10000\clftsWidth3 \clheight540 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx8640 -\pard\intbl\itap1\pardeftab720\partightenfactor0 - -\f0 \cf4 \kerning1\expnd-2\expndtw-10 -\up0 \nosupersub \ulnone Please provide a description of your project. What does the project aim to achieve? Why do you need to collect personal data and how will you use the data to support your project? -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone \cell \row - -\itap1\trowd \taflags1 \trgaph108\trleft-108 \trbrdrl\brdrnil \trbrdrt\brdrnil \trbrdrr\brdrnil -\clvertalt \clshdrawnil \clwWidth10000\clftsWidth3 \clheight820 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx8640 -\pard\intbl\itap1\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 -\cf0 -\f0 \cf4 \kerning1\expnd-2\expndtw-10 -\up0 \nosupersub \ulnone \ -\pard\intbl\itap1\pardeftab720\partightenfactor0 -\cf4 \ -\pard\intbl\itap1\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 - -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone -\f0 \cf2 \kerning1\expnd-1\expndtw-4 -\up0 \nosupersub \ulnone \cell \lastrow\row -\pard\pardeftab720\partightenfactor0 -\cf2 \ - -\itap1\trowd \taflags1 \trgaph108\trleft-108 \trbrdrt\brdrnil \trbrdrl\brdrnil \trbrdrr\brdrnil -\clvertalt \clcbpat2 \clwWidth10000\clftsWidth3 \clheight260 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx8640 -\pard\intbl\itap1\pardeftab720\partightenfactor0 -\cf3 \kerning1\expnd-2\expndtw-10 -Lawful basis -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone \cell \row - -\itap1\trowd \taflags1 \trgaph108\trleft-108 \trbrdrl\brdrnil \trbrdrr\brdrnil -\clvertalt \clshdrawnil \clwWidth10000\clftsWidth3 \clheight540 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx8640 -\pard\intbl\itap1\pardeftab720\partightenfactor0 - -\f0 \cf4 \kerning1\expnd-2\expndtw-10 -\up0 \nosupersub \ulnone Will you be processing personal data to meet a statutory duty or power? If so, please specify the legislation and associated duty or power. -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone \cell \row - -\itap1\trowd \taflags1 \trgaph108\trleft-108 \trbrdrl\brdrnil \trbrdrr\brdrnil -\clvertalt \clshdrawnil \clwWidth10000\clftsWidth3 \clheight1100 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx8640 -\pard\intbl\itap1\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 -\cf0 -\f0 \cf4 \kerning1\expnd-2\expndtw-10 -\up0 \nosupersub \ulnone \ -\pard\intbl\itap1\pardeftab720\partightenfactor0 -\cf4 \ -\ -\pard\intbl\itap1\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 - -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone \cell \row - -\itap1\trowd \taflags1 \trgaph108\trleft-108 \trbrdrl\brdrnil \trbrdrr\brdrnil -\clvertalt \clcbpat3 \clwWidth10000\clftsWidth3 \clheight540 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx8640 -\pard\intbl\itap1\pardeftab720\partightenfactor0 - -\f0 \cf4 \kerning1\expnd-2\expndtw-10 -\up0 \nosupersub \ulnone If not, is there other legislation which supports your processing? If so, please specify the legislation and supporting argument here -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone \cell \row - -\itap1\trowd \taflags1 \trgaph108\trleft-108 \trbrdrl\brdrnil \trbrdrr\brdrnil -\clvertalt \clshdrawnil \clwWidth10000\clftsWidth3 \clheight1120 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx8640 -\pard\intbl\itap1\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 -\cf0 -\f0 \cf4 \kerning1\expnd-2\expndtw-10 -\up0 \nosupersub \ulnone \ -\pard\intbl\itap1\pardeftab720\partightenfactor0 -\cf4 \ -\ -\pard\intbl\itap1\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 - -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone \cell \row - -\itap1\trowd \taflags1 \trgaph108\trleft-108 \trbrdrl\brdrnil \trbrdrr\brdrnil -\clvertalt \clshdrawnil \clwWidth10000\clftsWidth3 \clheight540 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx8640 -\pard\intbl\itap1\pardeftab720\partightenfactor0 - -\f0 \cf4 \kerning1\expnd-2\expndtw-10 -\up0 \nosupersub \ulnone If existing legislation does not apply, please specify the lawful basis you are relying upon. You may find the ICO\'92s {\field{\*\fldinst{HYPERLINK "https://ico.org.uk/for-organisations/gdpr-resources/lawful-basis-interactive-guidance-tool/"}}{\fldrslt \cf5 \kerning1\expnd-2\expndtw-10 -\ul \ulc5 interactive tool}} helpful. -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone \cell \row - -\itap1\trowd \taflags1 \trgaph108\trleft-108 \trbrdrl\brdrnil \trbrdrt\brdrnil \trbrdrr\brdrnil -\clvertalt \clshdrawnil \clwWidth10000\clftsWidth3 \clheight1100 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx8640 -\pard\intbl\itap1\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 -\cf0 -\f0 \cf4 \kerning1\expnd-2\expndtw-10 -\up0 \nosupersub \ulnone \ -\pard\intbl\itap1\pardeftab720\partightenfactor0 -\cf4 \ -\ -\pard\intbl\itap1\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 - -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone -\f0 \cf2 \kerning1\expnd-1\expndtw-4 -\up0 \nosupersub \ulnone \cell \lastrow\row -\pard\pardeftab720\partightenfactor0 -\cf2 \ - -\itap1\trowd \taflags1 \trgaph108\trleft-108 \trbrdrt\brdrnil \trbrdrl\brdrnil \trbrdrr\brdrnil -\clvertalt \clcbpat2 \clwWidth10000\clftsWidth3 \clheight260 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx8640 -\pard\intbl\itap1\pardeftab720\partightenfactor0 -\cf3 \kerning1\expnd-2\expndtw-10 -What personal data will be processed? -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone \cell \row - -\itap1\trowd \taflags1 \trgaph108\trleft-108 \trbrdrl\brdrnil \trbrdrr\brdrnil -\clvertalt \clshdrawnil \clwWidth10000\clftsWidth3 \clheight260 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx8640 -\pard\intbl\itap1\pardeftab720\partightenfactor0 - -\f0 \cf4 \kerning1\expnd-2\expndtw-10 -\up0 \nosupersub \ulnone Who are the data subjects (people whose personal data you will be processing)?: -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone \cell \row - -\itap1\trowd \taflags1 \trgaph108\trleft-108 \trbrdrl\brdrnil \trbrdrr\brdrnil -\clvertalt \clshdrawnil \clwWidth10000\clftsWidth3 \clheight820 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx8640 -\pard\intbl\itap1\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 -\cf0 -\f0 \cf4 \kerning1\expnd-2\expndtw-10 -\up0 \nosupersub \ulnone \ -\pard\intbl\itap1\pardeftab720\partightenfactor0 -\cf4 \ -\pard\intbl\itap1\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 - -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone \cell \row - -\itap1\trowd \taflags1 \trgaph108\trleft-108 \trbrdrl\brdrnil \trbrdrr\brdrnil -\clvertalt \clshdrawnil \clwWidth10000\clftsWidth3 \clheight260 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx8640 -\pard\intbl\itap1\pardeftab720\partightenfactor0 - -\f0 \cf4 \kerning1\expnd-2\expndtw-10 -\up0 \nosupersub \ulnone What personal data will be processed? (name, address, system ID etc) -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone \cell \row - -\itap1\trowd \taflags1 \trgaph108\trleft-108 \trbrdrl\brdrnil \trbrdrr\brdrnil -\clvertalt \clshdrawnil \clwWidth10000\clftsWidth3 \clheight840 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx8640 -\pard\intbl\itap1\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 -\cf0 -\f0 \cf4 \kerning1\expnd-2\expndtw-10 -\up0 \nosupersub \ulnone \ -\pard\intbl\itap1\pardeftab720\partightenfactor0 -\cf4 \ -\pard\intbl\itap1\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 - -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone \cell \row - -\itap1\trowd \taflags1 \trgaph108\trleft-108 \trbrdrl\brdrnil \trbrdrr\brdrnil -\clvertalt \clshdrawnil \clwWidth10000\clftsWidth3 \clheight260 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx8640 -\pard\intbl\itap1\pardeftab720\partightenfactor0 - -\f0 \cf4 \kerning1\expnd-2\expndtw-10 -\up0 \nosupersub \ulnone What Special Category data will be processed? (ethnicity, religious belief, health etc) -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone \cell \row - -\itap1\trowd \taflags1 \trgaph108\trleft-108 \trbrdrl\brdrnil \trbrdrr\brdrnil -\clvertalt \clshdrawnil \clwWidth10000\clftsWidth3 \clheight820 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx8640 -\pard\intbl\itap1\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 -\cf0 -\f0 \cf4 \kerning1\expnd-2\expndtw-10 -\up0 \nosupersub \ulnone \ -\pard\intbl\itap1\pardeftab720\partightenfactor0 -\cf4 \ -\pard\intbl\itap1\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 - -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone \cell \row - -\itap1\trowd \taflags1 \trgaph108\trleft-108 \trbrdrl\brdrnil \trbrdrr\brdrnil -\clvertalt \clshdrawnil \clwWidth10000\clftsWidth3 \clheight540 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx8640 -\pard\intbl\itap1\pardeftab720\partightenfactor0 - -\f0 \cf4 \kerning1\expnd-2\expndtw-10 -\up0 \nosupersub \ulnone What Criminal data will be processed? (convictions, allegations etc)\ -\pard\intbl\itap1\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 - -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone \cell \row - -\itap1\trowd \taflags1 \trgaph108\trleft-108 \trbrdrl\brdrnil \trbrdrt\brdrnil \trbrdrr\brdrnil -\clvertalt \clshdrawnil \clwWidth10000\clftsWidth3 \clheight820 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx8640 -\pard\intbl\itap1\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 -\cf0 -\f0 \cf4 \kerning1\expnd-2\expndtw-10 -\up0 \nosupersub \ulnone \ -\pard\intbl\itap1\pardeftab720\partightenfactor0 -\cf4 \ -\pard\intbl\itap1\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 - -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone -\f0 \cf2 \kerning1\expnd-1\expndtw-4 -\up0 \nosupersub \ulnone \cell \lastrow\row -\pard\pardeftab720\partightenfactor0 -\cf2 \ -\ - -\itap1\trowd \taflags1 \trgaph108\trleft-108 \trbrdrt\brdrnil \trbrdrl\brdrnil \trbrdrr\brdrnil -\clvertalt \clcbpat2 \clwWidth10000\clftsWidth3 \clheight260 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx8640 -\pard\intbl\itap1\pardeftab720\partightenfactor0 -\cf3 \kerning1\expnd-2\expndtw-10 -Digital Services -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone \cell \row - -\itap1\trowd \taflags1 \trgaph108\trleft-108 \trbrdrl\brdrnil \trbrdrr\brdrnil -\clvertalt \clshdrawnil \clwWidth10000\clftsWidth3 \clheight820 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx8640 -\pard\intbl\itap1\pardeftab720\partightenfactor0 - -\f0 \cf4 \kerning1\expnd-2\expndtw-10 -\up0 \nosupersub \ulnone If your project involves the purchase or use of new technology, please confirm that you have approached IDS to assess the security of the new system. Please provide the name of the contact in IDS -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone \cell \row - -\itap1\trowd \taflags1 \trgaph108\trleft-108 \trbrdrl\brdrnil \trbrdrt\brdrnil \trbrdrr\brdrnil -\clvertalt \clshdrawnil \clwWidth10000\clftsWidth3 \clheight1100 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx8640 -\pard\intbl\itap1\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 -\cf0 -\f0 \cf4 \kerning1\expnd-2\expndtw-10 -\up0 \nosupersub \ulnone \ -\pard\intbl\itap1\pardeftab720\partightenfactor0 -\cf4 \ -\ -\pard\intbl\itap1\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 - -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone -\f0 \cf2 \kerning1\expnd-1\expndtw-4 -\up0 \nosupersub \ulnone \cell \lastrow\row -\pard\pardeftab720\partightenfactor0 -\cf2 \ - -\itap1\trowd \taflags1 \trgaph108\trleft-108 \trbrdrt\brdrnil \trbrdrl\brdrnil \trbrdrr\brdrnil -\clvertalt \clcbpat2 \clwWidth10000\clftsWidth3 \clheight260 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx8640 -\pard\intbl\itap1\pardeftab720\partightenfactor0 -\cf3 \kerning1\expnd-2\expndtw-10 -Procurement -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone \cell \row - -\itap1\trowd \taflags1 \trgaph108\trleft-108 \trbrdrl\brdrnil \trbrdrr\brdrnil -\clvertalt \clshdrawnil \clwWidth10000\clftsWidth3 \clheight540 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx8640 -\pard\intbl\itap1\pardeftab720\partightenfactor0 - -\f0 \cf4 \kerning1\expnd-2\expndtw-10 -\up0 \nosupersub \ulnone If your project involves a third party and/or contractor please provide their details, the contract number and, if possible, attach a copy of the contract -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone \cell \row - -\itap1\trowd \taflags1 \trgaph108\trleft-108 \trbrdrl\brdrnil \trbrdrt\brdrnil \trbrdrr\brdrnil -\clvertalt \clshdrawnil \clwWidth10000\clftsWidth3 \clheight1100 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx8640 -\pard\intbl\itap1\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 -\cf0 -\f0 \cf4 \kerning1\expnd-2\expndtw-10 -\up0 \nosupersub \ulnone \ -\pard\intbl\itap1\pardeftab720\partightenfactor0 -\cf4 \ -\ -\pard\intbl\itap1\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 - -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone -\f0 \cf2 \kerning1\expnd-1\expndtw-4 -\up0 \nosupersub \ulnone \cell \lastrow\row -\pard\pardeftab720\partightenfactor0 -\cf2 \ - -\itap1\trowd \taflags1 \trgaph108\trleft-108 \trbrdrt\brdrnil \trbrdrl\brdrnil \trbrdrt\brdrnil \trbrdrr\brdrnil -\clvertalt \clcbpat2 \clwWidth10000\clftsWidth3 \clheight260 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx8640 -\pard\intbl\itap1\pardeftab720\partightenfactor0 -\cf3 \kerning1\expnd-2\expndtw-10 -Does the project involve any of the following types of processing? -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone -\f0 \cf2 \kerning1\expnd-1\expndtw-4 -\up0 \nosupersub \ulnone \cell \lastrow\row -\pard\pardeftab720\partightenfactor0 -\cf2 \ - -\itap1\trowd \taflags1 \trgaph108\trleft-108 \trbrdrt\brdrnil \trbrdrl\brdrnil \trbrdrr\brdrnil -\clvertalt \clcbpat2 \clwWidth280\clftsWidth3 \clheight260 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx2880 -\clvertalt \clcbpat2 \clwWidth6160\clftsWidth3 \clheight260 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx5760 -\clvertalt \clcbpat2 \clwWidth3200\clftsWidth3 \clheight260 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx8640 -\pard\intbl\itap1\pardeftab720\partightenfactor0 -\cf3 \kerning1\expnd-2\expndtw-10 -# -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone \cell -\pard\intbl\itap1\pardeftab720\partightenfactor0 - -\f0 \cf3 \kerning1\expnd-2\expndtw-10 -\up0 \nosupersub \ulnone Type of processing -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone \cell -\pard\intbl\itap1\pardeftab720\partightenfactor0 - -\f0 \cf3 \kerning1\expnd-2\expndtw-10 -\up0 \nosupersub \ulnone Yes/ No/ Don\'92t Know -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone \cell \row - -\itap1\trowd \taflags1 \trgaph108\trleft-108 \trbrdrl\brdrnil \trbrdrr\brdrnil -\clvertalt \clcbpat2 \clwWidth280\clftsWidth3 \clheight540 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx2880 -\clvertalt \clshdrawnil \clwWidth6160\clftsWidth3 \clheight540 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx5760 -\clvertalt \clshdrawnil \clwWidth3200\clftsWidth3 \clheight540 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx8640 -\pard\intbl\itap1\pardeftab720\partightenfactor0 - -\f0 \cf3 \kerning1\expnd-2\expndtw-10 -\up0 \nosupersub \ulnone 01 -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone \cell -\pard\intbl\itap1\pardeftab720\partightenfactor0 - -\f0 \cf4 \kerning1\expnd-2\expndtw-10 -\up0 \nosupersub \ulnone Uses new technology or IT system that we haven\'92t used before\ -\pard\intbl\itap1\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 - -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone \cell -\pard\intbl\itap1\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 -\cf0 \cell \row - -\itap1\trowd \taflags1 \trgaph108\trleft-108 \trbrdrl\brdrnil \trbrdrr\brdrnil -\clvertalt \clcbpat2 \clwWidth280\clftsWidth3 \clheight1100 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx2880 -\clvertalt \clshdrawnil \clwWidth6160\clftsWidth3 \clheight1100 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx5760 -\clvertalt \clshdrawnil \clwWidth3200\clftsWidth3 \clheight1100 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx8640 -\pard\intbl\itap1\pardeftab720\partightenfactor0 - -\f0 \cf3 \kerning1\expnd-2\expndtw-10 -\up0 \nosupersub \ulnone 02 -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone \cell -\pard\intbl\itap1\pardeftab720\partightenfactor0 - -\f0 \cf4 \kerning1\expnd-2\expndtw-10 -\up0 \nosupersub \ulnone Requires that the data be processed (this includes being stored and accessed by sub-contractors or IT support) outside the UK or the EU?\ -\pard\intbl\itap1\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 - -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone \cell -\pard\intbl\itap1\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 -\cf0 \cell \row - -\itap1\trowd \taflags1 \trgaph108\trleft-108 \trbrdrl\brdrnil \trbrdrr\brdrnil -\clvertalt \clcbpat2 \clwWidth280\clftsWidth3 \clheight820 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx2880 -\clvertalt \clshdrawnil \clwWidth6160\clftsWidth3 \clheight820 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx5760 -\clvertalt \clshdrawnil \clwWidth3200\clftsWidth3 \clheight820 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx8640 -\pard\intbl\itap1\pardeftab720\partightenfactor0 - -\f0 \cf3 \kerning1\expnd-2\expndtw-10 -\up0 \nosupersub \ulnone 03 -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone \cell -\pard\intbl\itap1\pardeftab720\partightenfactor0 - -\f0 \cf4 \kerning1\expnd-2\expndtw-10 -\up0 \nosupersub \ulnone Uses existing data for a new and unexpected or more intrusive purpose (such as tracking people\'92s behaviour or activity)\ -\pard\intbl\itap1\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 - -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone \cell -\pard\intbl\itap1\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 -\cf0 \cell \row - -\itap1\trowd \taflags1 \trgaph108\trleft-108 \trbrdrl\brdrnil \trbrdrr\brdrnil -\clvertalt \clcbpat2 \clwWidth280\clftsWidth3 \clheight1680 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx2880 -\clvertalt \clshdrawnil \clwWidth6160\clftsWidth3 \clheight1680 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx5760 -\clvertalt \clshdrawnil \clwWidth3200\clftsWidth3 \clheight1680 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx8640 -\pard\intbl\itap1\pardeftab720\partightenfactor0 - -\f0 \cf3 \kerning1\expnd-2\expndtw-10 -\up0 \nosupersub \ulnone 04 -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone \cell -\pard\intbl\itap1\pardeftab720\partightenfactor0 - -\f0 \cf4 \kerning1\expnd-2\expndtw-10 -\up0 \nosupersub \ulnone Will require new collection or a new purpose of special category or criminal offence data. (Such as identifying people in a particular group or demographic and proposing a course of action for them e.g. determining access to a service or benefit) or for large numbers of vulnerable people\ -\pard\intbl\itap1\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 - -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone \cell -\pard\intbl\itap1\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 -\cf0 \cell \row - -\itap1\trowd \taflags1 \trgaph108\trleft-108 \trbrdrl\brdrnil \trbrdrr\brdrnil -\clvertalt \clcbpat2 \clwWidth280\clftsWidth3 \clheight540 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx2880 -\clvertalt \clshdrawnil \clwWidth6160\clftsWidth3 \clheight540 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx5760 -\clvertalt \clshdrawnil \clwWidth3200\clftsWidth3 \clheight540 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx8640 -\pard\intbl\itap1\pardeftab720\partightenfactor0 - -\f0 \cf3 \kerning1\expnd-2\expndtw-10 -\up0 \nosupersub \ulnone 05 -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone \cell -\pard\intbl\itap1\pardeftab720\partightenfactor0 - -\f0 \cf4 \kerning1\expnd-2\expndtw-10 -\up0 \nosupersub \ulnone Involves Adult Social Care records for non-direct care purposes\ -\pard\intbl\itap1\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 - -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone \cell -\pard\intbl\itap1\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 -\cf0 \cell \row - -\itap1\trowd \taflags1 \trgaph108\trleft-108 \trbrdrl\brdrnil \trbrdrr\brdrnil -\clvertalt \clcbpat2 \clwWidth280\clftsWidth3 \clheight820 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx2880 -\clvertalt \clshdrawnil \clwWidth6160\clftsWidth3 \clheight820 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx5760 -\clvertalt \clshdrawnil \clwWidth3200\clftsWidth3 \clheight820 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx8640 -\pard\intbl\itap1\pardeftab720\partightenfactor0 - -\f0 \cf3 \kerning1\expnd-2\expndtw-10 -\up0 \nosupersub \ulnone 06 -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone \cell -\pard\intbl\itap1\pardeftab720\partightenfactor0 - -\f0 \cf4 \kerning1\expnd-2\expndtw-10 -\up0 \nosupersub \ulnone Involves processing personal data about individuals which could cause physical harm to them if there was a data breach\ -\pard\intbl\itap1\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 - -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone \cell -\pard\intbl\itap1\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 -\cf0 \cell \row - -\itap1\trowd \taflags1 \trgaph108\trleft-108 \trbrdrl\brdrnil \trbrdrr\brdrnil -\clvertalt \clcbpat2 \clwWidth280\clftsWidth3 \clheight820 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx2880 -\clvertalt \clshdrawnil \clwWidth6160\clftsWidth3 \clheight820 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx5760 -\clvertalt \clshdrawnil \clwWidth3200\clftsWidth3 \clheight820 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx8640 -\pard\intbl\itap1\pardeftab720\partightenfactor0 - -\f0 \cf3 \kerning1\expnd-2\expndtw-10 -\up0 \nosupersub \ulnone 07 -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone \cell -\pard\intbl\itap1\pardeftab720\partightenfactor0 - -\f0 \cf4 \kerning1\expnd-2\expndtw-10 -\up0 \nosupersub \ulnone Will require the processing of Children's data to offer online services, or to profile them or to market services to them\ -\pard\intbl\itap1\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 - -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone \cell -\pard\intbl\itap1\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 -\cf0 \cell \row - -\itap1\trowd \taflags1 \trgaph108\trleft-108 \trbrdrl\brdrnil \trbrdrr\brdrnil -\clvertalt \clcbpat2 \clwWidth280\clftsWidth3 \clheight1380 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx2880 -\clvertalt \clshdrawnil \clwWidth6160\clftsWidth3 \clheight1380 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx5760 -\clvertalt \clshdrawnil \clwWidth3200\clftsWidth3 \clheight1380 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx8640 -\pard\intbl\itap1\pardeftab720\partightenfactor0 - -\f0 \cf3 \kerning1\expnd-2\expndtw-10 -\up0 \nosupersub \ulnone 08 -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone \cell -\pard\intbl\itap1\pardeftab720\partightenfactor0 - -\f0 \cf4 \kerning1\expnd-2\expndtw-10 -\up0 \nosupersub \ulnone Will Make Automated Decisions About The Individual (where only computers are involved in the decision making process and this may have a significant impact upon the individual such as their ability to access services, opportunities or benefits)\ -\pard\intbl\itap1\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 - -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone \cell -\pard\intbl\itap1\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 -\cf0 \cell \row - -\itap1\trowd \taflags1 \trgaph108\trleft-108 \trbrdrl\brdrnil \trbrdrr\brdrnil -\clvertalt \clcbpat2 \clwWidth280\clftsWidth3 \clheight540 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx2880 -\clvertalt \clshdrawnil \clwWidth6160\clftsWidth3 \clheight540 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx5760 -\clvertalt \clshdrawnil \clwWidth3200\clftsWidth3 \clheight540 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx8640 -\pard\intbl\itap1\pardeftab720\partightenfactor0 - -\f0 \cf3 \kerning1\expnd-2\expndtw-10 -\up0 \nosupersub \ulnone 09 -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone \cell -\pard\intbl\itap1\pardeftab720\partightenfactor0 - -\f0 \cf4 \kerning1\expnd-2\expndtw-10 -\up0 \nosupersub \ulnone Involves surveillance or monitoring of staff or the public\ -\pard\intbl\itap1\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 - -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone \cell -\pard\intbl\itap1\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 -\cf0 \cell \row - -\itap1\trowd \taflags1 \trgaph108\trleft-108 \trbrdrl\brdrnil \trbrdrr\brdrnil -\clvertalt \clcbpat2 \clwWidth280\clftsWidth3 \clheight540 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx2880 -\clvertalt \clshdrawnil \clwWidth6160\clftsWidth3 \clheight540 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx5760 -\clvertalt \clshdrawnil \clwWidth3200\clftsWidth3 \clheight540 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx8640 -\pard\intbl\itap1\pardeftab720\partightenfactor0 - -\f0 \cf3 \kerning1\expnd-2\expndtw-10 -\up0 \nosupersub \ulnone 10 -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone \cell -\pard\intbl\itap1\pardeftab720\partightenfactor0 - -\f0 \cf4 \kerning1\expnd-2\expndtw-10 -\up0 \nosupersub \ulnone Involves two or more organisations linking or pooling data\ -\pard\intbl\itap1\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 - -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone \cell -\pard\intbl\itap1\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 -\cf0 \cell \row - -\itap1\trowd \taflags1 \trgaph108\trleft-108 \trbrdrl\brdrnil \trbrdrr\brdrnil -\clvertalt \clcbpat2 \clwWidth280\clftsWidth3 \clheight540 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx2880 -\clvertalt \clshdrawnil \clwWidth6160\clftsWidth3 \clheight540 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx5760 -\clvertalt \clshdrawnil \clwWidth3200\clftsWidth3 \clheight540 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx8640 -\pard\intbl\itap1\pardeftab720\partightenfactor0 - -\f0 \cf3 \kerning1\expnd-2\expndtw-10 -\up0 \nosupersub \ulnone 11 -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone \cell -\pard\intbl\itap1\pardeftab720\partightenfactor0 - -\f0 \cf4 \kerning1\expnd-2\expndtw-10 -\up0 \nosupersub \ulnone Will combine or match data from multiple sources\ -\pard\intbl\itap1\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 - -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone \cell -\pard\intbl\itap1\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 -\cf0 \cell \row - -\itap1\trowd \taflags1 \trgaph108\trleft-108 \trbrdrl\brdrnil \trbrdrr\brdrnil -\clvertalt \clcbpat2 \clwWidth280\clftsWidth3 \clheight820 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx2880 -\clvertalt \clshdrawnil \clwWidth6160\clftsWidth3 \clheight820 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx5760 -\clvertalt \clshdrawnil \clwWidth3200\clftsWidth3 \clheight820 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx8640 -\pard\intbl\itap1\pardeftab720\partightenfactor0 - -\f0 \cf3 \kerning1\expnd-2\expndtw-10 -\up0 \nosupersub \ulnone 12 -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone \cell -\pard\intbl\itap1\pardeftab720\partightenfactor0 - -\f0 \cf4 \kerning1\expnd-2\expndtw-10 -\up0 \nosupersub \ulnone Will require the processing of biometric or genetic data (such as fingerprints or facial recognition)\ -\pard\intbl\itap1\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 - -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone \cell -\pard\intbl\itap1\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 -\cf0 \cell \row - -\itap1\trowd \taflags1 \trgaph108\trleft-108 \trbrdrl\brdrnil \trbrdrt\brdrnil \trbrdrr\brdrnil -\clvertalt \clcbpat2 \clwWidth280\clftsWidth3 \clheight1120 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx2880 -\clvertalt \clshdrawnil \clwWidth6160\clftsWidth3 \clheight1120 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx5760 -\clvertalt \clshdrawnil \clwWidth3200\clftsWidth3 \clheight1120 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx8640 -\pard\intbl\itap1\pardeftab720\partightenfactor0 - -\f0 \cf3 \kerning1\expnd-2\expndtw-10 -\up0 \nosupersub \ulnone 13 -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone \cell -\pard\intbl\itap1\pardeftab720\partightenfactor0 - -\f0 \cf4 \kerning1\expnd-2\expndtw-10 -\up0 \nosupersub \ulnone Will personal data be processed without providing a privacy notice to the data subject? (This means we would be processing their data without telling them)\ -\pard\intbl\itap1\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 - -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone \cell -\pard\intbl\itap1\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 -\cf0 -\f0 \cf2 \kerning1\expnd-1\expndtw-4 -\up0 \nosupersub \ulnone \cell \lastrow\row -\pard\pardeftab720\partightenfactor0 -\cf2 \ -\ - -\itap1\trowd \taflags1 \trgaph108\trleft-108 \trbrdrt\brdrnil \trbrdrl\brdrnil \trbrdrr\brdrnil -\clvertalt \clcbpat2 \clwWidth10000\clftsWidth3 \clheight260 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx8640 -\pard\intbl\itap1\pardeftab720\partightenfactor0 -\cf3 \kerning1\expnd-2\expndtw-10 -Risk Assessment -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone \cell \row - -\itap1\trowd \taflags1 \trgaph108\trleft-108 \trbrdrl\brdrnil \trbrdrr\brdrnil -\clvertalt \clshdrawnil \clwWidth10000\clftsWidth3 \clheight540 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx8640 -\pard\intbl\itap1\pardeftab720\partightenfactor0 - -\f0 \cf4 \kerning1\expnd-2\expndtw-10 -\up0 \nosupersub \ulnone If you answered \'93Yes\'94 to any of the above questions, please explain what you are doing to mitigate risks associated with this type of processing. -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone \cell \row - -\itap1\trowd \taflags1 \trgaph108\trleft-108 \trbrdrl\brdrnil \trbrdrr\brdrnil -\clvertalt \clshdrawnil \clwWidth10000\clftsWidth3 \clheight1100 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx8640 -\pard\intbl\itap1\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 -\cf0 -\f0 \cf4 \kerning1\expnd-2\expndtw-10 -\up0 \nosupersub \ulnone \ -\pard\intbl\itap1\pardeftab720\partightenfactor0 -\cf4 \ -\ -\pard\intbl\itap1\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 - -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone \cell \row - -\itap1\trowd \taflags1 \trgaph108\trleft-108 \trbrdrl\brdrnil \trbrdrr\brdrnil -\clvertalt \clshdrawnil \clwWidth10000\clftsWidth3 \clheight540 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx8640 -\pard\intbl\itap1\pardeftab720\partightenfactor0 - -\f0 \cf4 \kerning1\expnd-2\expndtw-10 -\up0 \nosupersub \ulnone Please also describe any general mitigation or precautions you are taking to reduce information risk with this project. -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone \cell \row - -\itap1\trowd \taflags1 \trgaph108\trleft-108 \trbrdrl\brdrnil \trbrdrt\brdrnil \trbrdrr\brdrnil -\clvertalt \clshdrawnil \clwWidth10000\clftsWidth3 \clheight1120 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx8640 -\pard\intbl\itap1\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 -\cf0 -\f0 \cf4 \kerning1\expnd-2\expndtw-10 -\up0 \nosupersub \ulnone \ -\pard\intbl\itap1\pardeftab720\partightenfactor0 -\cf4 \ -\ -\pard\intbl\itap1\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 - -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone -\f0 \cf2 \kerning1\expnd-1\expndtw-4 -\up0 \nosupersub \ulnone \cell \lastrow\row -\pard\pardeftab720\partightenfactor0 -\cf4 \kerning1\expnd-1\expndtw-4 -\ -\ -\ -\ -\ -\ -\ - -\itap1\trowd \taflags1 \trgaph108\trleft-108 \trbrdrt\brdrnil \trbrdrl\brdrnil \trbrdrr\brdrnil -\clvertalt \clcbpat2 \clwWidth10000\clftsWidth3 \clheight260 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx8640 -\pard\intbl\itap1\pardeftab720\partightenfactor0 -\cf3 \kerning1\expnd-2\expndtw-10 -Ethics - Engagement -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone \cell \row - -\itap1\trowd \taflags1 \trgaph108\trleft-108 \trbrdrl\brdrnil \trbrdrr\brdrnil -\clvertalt \clcbpat6 \clwWidth10000\clftsWidth3 \clheight820 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx8640 -\pard\intbl\itap1\pardeftab720\partightenfactor0 - -\f0 \cf4 \kerning1\expnd-2\expndtw-10 -\up0 \nosupersub \ulnone If you will be consulting or engaging with any residents, or other stakeholders (staff, practitioners, decision makers) as part of this project please answer the following questions, otherwise leave this section blank -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone \cell \row - -\itap1\trowd \taflags1 \trgaph108\trleft-108 \trbrdrl\brdrnil \trbrdrr\brdrnil -\clvertalt \clshdrawnil \clwWidth10000\clftsWidth3 \clheight260 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx8640 -\pard\intbl\itap1\pardeftab720\partightenfactor0 - -\f0 \cf4 \kerning1\expnd-2\expndtw-10 -\up0 \nosupersub \ulnone Which groups or communities do you plan to consult? -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone \cell \row - -\itap1\trowd \taflags1 \trgaph108\trleft-108 \trbrdrl\brdrnil \trbrdrr\brdrnil -\clvertalt \clshdrawnil \clwWidth10000\clftsWidth3 \clheight1100 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx8640 -\pard\intbl\itap1\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 -\cf0 -\f0 \cf4 \kerning1\expnd-2\expndtw-10 -\up0 \nosupersub \ulnone \ -\pard\intbl\itap1\pardeftab720\partightenfactor0 -\cf4 \ -\ -\pard\intbl\itap1\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 - -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone \cell \row - -\itap1\trowd \taflags1 \trgaph108\trleft-108 \trbrdrl\brdrnil \trbrdrr\brdrnil -\clvertalt \clshdrawnil \clwWidth10000\clftsWidth3 \clheight280 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx8640 -\pard\intbl\itap1\pardeftab720\partightenfactor0 - -\f0 \cf4 \kerning1\expnd-2\expndtw-10 -\up0 \nosupersub \ulnone Will any of your participants be under 18 or could be considered vulnerable? -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone \cell \row - -\itap1\trowd \taflags1 \trgaph108\trleft-108 \trbrdrl\brdrnil \trbrdrr\brdrnil -\clvertalt \clshdrawnil \clwWidth10000\clftsWidth3 \clheight1100 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx8640 -\pard\intbl\itap1\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 -\cf0 -\f0 \cf4 \kerning1\expnd-2\expndtw-10 -\up0 \nosupersub \ulnone \ -\pard\intbl\itap1\pardeftab720\partightenfactor0 -\cf4 \ -\ -\pard\intbl\itap1\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 - -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone \cell \row - -\itap1\trowd \taflags1 \trgaph108\trleft-108 \trbrdrl\brdrnil \trbrdrr\brdrnil -\clvertalt \clshdrawnil \clwWidth10000\clftsWidth3 \clheight260 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx8640 -\pard\intbl\itap1\pardeftab720\partightenfactor0 - -\f0 \cf4 \kerning1\expnd-2\expndtw-10 -\up0 \nosupersub \ulnone How will you engage / recruit people to take part? -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone \cell \row - -\itap1\trowd \taflags1 \trgaph108\trleft-108 \trbrdrl\brdrnil \trbrdrr\brdrnil -\clvertalt \clshdrawnil \clwWidth10000\clftsWidth3 \clheight1100 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx8640 -\pard\intbl\itap1\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 -\cf0 -\f0 \cf4 \kerning1\expnd-2\expndtw-10 -\up0 \nosupersub \ulnone \ -\pard\intbl\itap1\pardeftab720\partightenfactor0 -\cf4 \ -\ -\pard\intbl\itap1\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 - -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone \cell \row - -\itap1\trowd \taflags1 \trgaph108\trleft-108 \trbrdrl\brdrnil \trbrdrr\brdrnil -\clvertalt \clshdrawnil \clwWidth10000\clftsWidth3 \clheight260 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx8640 -\pard\intbl\itap1\pardeftab720\partightenfactor0 - -\f0 \cf4 \kerning1\expnd-2\expndtw-10 -\up0 \nosupersub \ulnone What methods will you use to engage (for example, focus groups / surveys)? -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone \cell \row - -\itap1\trowd \taflags1 \trgaph108\trleft-108 \trbrdrl\brdrnil \trbrdrr\brdrnil -\clvertalt \clshdrawnil \clwWidth10000\clftsWidth3 \clheight1100 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx8640 -\pard\intbl\itap1\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 -\cf0 -\f0 \cf4 \kerning1\expnd-2\expndtw-10 -\up0 \nosupersub \ulnone \ -\pard\intbl\itap1\pardeftab720\partightenfactor0 -\cf4 \ -\ -\pard\intbl\itap1\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 - -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone \cell \row - -\itap1\trowd \taflags1 \trgaph108\trleft-108 \trbrdrl\brdrnil \trbrdrr\brdrnil -\clvertalt \clshdrawnil \clwWidth10000\clftsWidth3 \clheight260 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx8640 -\pard\intbl\itap1\pardeftab720\partightenfactor0 - -\f0 \cf4 \kerning1\expnd-2\expndtw-10 -\up0 \nosupersub \ulnone What process will you use when seeking to obtain consent? (please attach or include a link to forms) -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone \cell \row - -\itap1\trowd \taflags1 \trgaph108\trleft-108 \trbrdrl\brdrnil \trbrdrr\brdrnil -\clvertalt \clshdrawnil \clwWidth10000\clftsWidth3 \clheight1100 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx8640 -\pard\intbl\itap1\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 -\cf0 -\f0 \cf4 \kerning1\expnd-2\expndtw-10 -\up0 \nosupersub \ulnone \ -\pard\intbl\itap1\pardeftab720\partightenfactor0 -\cf4 \ -\ -\pard\intbl\itap1\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 - -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone \cell \row - -\itap1\trowd \taflags1 \trgaph108\trleft-108 \trbrdrl\brdrnil \trbrdrr\brdrnil -\clvertalt \clshdrawnil \clwWidth10000\clftsWidth3 \clheight260 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx8640 -\pard\intbl\itap1\pardeftab720\partightenfactor0 - -\f0 \cf4 \kerning1\expnd-2\expndtw-10 -\up0 \nosupersub \ulnone What are the risks to confidentiality or anonymity and how will these be mitigated? -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone \cell \row - -\itap1\trowd \taflags1 \trgaph108\trleft-108 \trbrdrl\brdrnil \trbrdrr\brdrnil -\clvertalt \clshdrawnil \clwWidth10000\clftsWidth3 \clheight1120 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx8640 -\pard\intbl\itap1\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 -\cf0 -\f0 \cf4 \kerning1\expnd-2\expndtw-10 -\up0 \nosupersub \ulnone \ -\pard\intbl\itap1\pardeftab720\partightenfactor0 -\cf4 \ -\ -\pard\intbl\itap1\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 - -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone \cell \row - -\itap1\trowd \taflags1 \trgaph108\trleft-108 \trbrdrl\brdrnil \trbrdrr\brdrnil -\clvertalt \clshdrawnil \clwWidth10000\clftsWidth3 \clheight260 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx8640 -\pard\intbl\itap1\pardeftab720\partightenfactor0 - -\f0 \cf4 \kerning1\expnd-2\expndtw-10 -\up0 \nosupersub \ulnone How will insight gained will be shared with stakeholders, including with people taking part? -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone \cell \row - -\itap1\trowd \taflags1 \trgaph108\trleft-108 \trbrdrl\brdrnil \trbrdrt\brdrnil \trbrdrr\brdrnil -\clvertalt \clshdrawnil \clwWidth10000\clftsWidth3 \clheight1100 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx8640 -\pard\intbl\itap1\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 -\cf0 -\f0 \cf4 \kerning1\expnd-2\expndtw-10 -\up0 \nosupersub \ulnone \ -\pard\intbl\itap1\pardeftab720\partightenfactor0 -\cf4 \ -\ -\pard\intbl\itap1\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 - -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone -\f0 \cf4 \kerning1\expnd-1\expndtw-4 -\up0 \nosupersub \ulnone \cell \lastrow\row -\pard\pardeftab720\sa240\partightenfactor0 -\cf4 \kerning1\expnd-1\expndtw-4 -\ -\pard\pardeftab720\sa120\qc\partightenfactor0 - -\fs32 \cf4 \kerning1\expnd0\expndtw0 Please return this form to your data protection lead or by emailing {\field{\*\fldinst{HYPERLINK "mailto:dp@islington.gov.uk"}}{\fldrslt \cf5 \ul \ulc5 dp@islington.gov.uk}}\ -\pard\pardeftab720\partightenfactor0 - -\fs24 \cf4 \page -\itap1\trowd \taflags1 \trgaph108\trleft-108 \trbrdrt\brdrnil \trbrdrl\brdrnil \trbrdrr\brdrnil -\clvertalt \clcbpat7 \clwWidth10000\clftsWidth3 \clheight260 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx8640 -\pard\intbl\itap1\pardeftab720\partightenfactor0 -\cf3 \kerning1\expnd-2\expndtw-10 -\ -To be completed by the Information Governance Team -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone \cell \row - -\itap1\trowd \taflags1 \trgaph108\trleft-108 \trbrdrl\brdrnil \trbrdrr\brdrnil -\clvertalt \clshdrawnil \clwWidth10000\clftsWidth3 \clheight540 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx8640 -\pard\intbl\itap1\pardeftab720\partightenfactor0 - -\f0 \cf4 \kerning1\expnd-2\expndtw-10 -\up0 \nosupersub \ulnone Please provide assessment as to whether a full DPIA is required. Please include any further recommendations or actions for this project. -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone \cell \row - -\itap1\trowd \taflags1 \trgaph108\trleft-108 \trbrdrl\brdrnil \trbrdrr\brdrnil -\clvertalt \clshdrawnil \clwWidth10000\clftsWidth3 \clheight1100 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx8640 -\pard\intbl\itap1\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 -\cf0 -\f0 \cf4 \kerning1\expnd-2\expndtw-10 -\up0 \nosupersub \ulnone \ -\pard\intbl\itap1\pardeftab720\partightenfactor0 -\cf4 \ -\ -\pard\intbl\itap1\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 - -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone \cell \row - -\itap1\trowd \taflags1 \trgaph108\trleft-108 \trbrdrl\brdrnil \trbrdrr\brdrnil -\clvertalt \clcbpat7 \clwWidth10000\clftsWidth3 \clheight260 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx8640 -\pard\intbl\itap1\pardeftab720\partightenfactor0 - -\f0 \cf3 \kerning1\expnd-2\expndtw-10 -\up0 \nosupersub \ulnone Ethics Score -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone \cell \row - -\itap1\trowd \taflags1 \trgaph108\trleft-108 \trbrdrl\brdrnil \trbrdrr\brdrnil -\clvertalt \clcbpat3 \clwWidth10000\clftsWidth3 \clheight840 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx8640 -\pard\intbl\itap1\pardeftab720\partightenfactor0 - -\f0 \cf4 \kerning1\expnd-2\expndtw-10 -\up0 \nosupersub \ulnone \ - NULL LOW MED HIGH\ -\pard\intbl\itap1\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 - -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone \cell \row - -\itap1\trowd \taflags1 \trgaph108\trleft-108 \trbrdrl\brdrnil \trbrdrr\brdrnil -\clvertalt \clcbpat7 \clwWidth10000\clftsWidth3 \clheight260 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx8640 -\pard\intbl\itap1\pardeftab720\partightenfactor0 - -\f0 \cf3 \kerning1\expnd-2\expndtw-10 -\up0 \nosupersub \ulnone Date Forwarded to Ethics Group -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone \cell \row - -\itap1\trowd \taflags1 \trgaph108\trleft-108 \trbrdrl\brdrnil \trbrdrr\brdrnil -\clvertalt \clcbpat3 \clwWidth10000\clftsWidth3 \clheight820 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx8640 -\pard\intbl\itap1\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 -\cf0 -\f0 \cf4 \kerning1\expnd-2\expndtw-10 -\up0 \nosupersub \ulnone \ -\pard\intbl\itap1\pardeftab720\partightenfactor0 -\cf4 \ -\pard\intbl\itap1\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 - -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone \cell \row - -\itap1\trowd \taflags1 \trgaph108\trleft-108 \trbrdrl\brdrnil \trbrdrr\brdrnil -\clvertalt \clcbpat7 \clwWidth10000\clftsWidth3 \clheight260 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx8640 -\pard\intbl\itap1\pardeftab720\partightenfactor0 - -\f0 \cf3 \kerning1\expnd-2\expndtw-10 -\up0 \nosupersub \ulnone IGT Reference -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone -\f0 \cf4 \kerning1\expnd-2\expndtw-10 -\up0 \nosupersub \ulnone \cell \row - -\itap1\trowd \taflags1 \trgaph108\trleft-108 \trbrdrl\brdrnil \trbrdrr\brdrnil -\clvertalt \clcbpat3 \clwWidth10000\clftsWidth3 \clheight540 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx8640 -\pard\intbl\itap1\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 - -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone \cell \row - -\itap1\trowd \taflags1 \trgaph108\trleft-108 \trbrdrl\brdrnil \trbrdrr\brdrnil -\clvertalt \clcbpat7 \clwWidth10000\clftsWidth3 \clheight260 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx8640 -\pard\intbl\itap1\pardeftab720\partightenfactor0 - -\f0 \cf3 \kerning1\expnd-2\expndtw-10 -\up0 \nosupersub \ulnone Name -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone -\f0 \cf4 \kerning1\expnd-2\expndtw-10 -\up0 \nosupersub \ulnone \cell \row - -\itap1\trowd \taflags1 \trgaph108\trleft-108 \trbrdrl\brdrnil \trbrdrr\brdrnil -\clvertalt \clshdrawnil \clwWidth10000\clftsWidth3 \clheight540 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx8640 -\pard\intbl\itap1\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 - -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone \cell \row - -\itap1\trowd \taflags1 \trgaph108\trleft-108 \trbrdrl\brdrnil \trbrdrr\brdrnil -\clvertalt \clcbpat7 \clwWidth10000\clftsWidth3 \clheight260 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx8640 -\pard\intbl\itap1\pardeftab720\partightenfactor0 - -\f0 \cf3 \kerning1\expnd-2\expndtw-10 -\up0 \nosupersub \ulnone Date -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone -\f0 \cf4 \kerning1\expnd-2\expndtw-10 -\up0 \nosupersub \ulnone \cell \row - -\itap1\trowd \taflags1 \trgaph108\trleft-108 \trbrdrl\brdrnil \trbrdrt\brdrnil \trbrdrr\brdrnil -\clvertalt \clshdrawnil \clwWidth10000\clftsWidth3 \clheight540 \clbrdrt\brdrs\brdrw20\brdrcf4 \clbrdrl\brdrs\brdrw20\brdrcf4 \clbrdrb\brdrs\brdrw20\brdrcf4 \clbrdrr\brdrs\brdrw20\brdrcf4 \clpadt80 \clpadl80 \clpadb80 \clpadr80 \gaph\cellx8640 -\pard\intbl\itap1\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 - -\f1 \cf0 \kerning1\expnd0\expndtw0 \up0 \nosupersub \ulnone -\f0 \cf4 \up0 \nosupersub \ulnone \cell \lastrow\row -} \ No newline at end of file diff --git a/driver.js.md b/driver.js.md deleted file mode 100644 index 91c0501..0000000 --- a/driver.js.md +++ /dev/null @@ -1,885 +0,0 @@ -Installation -Run one of the following commands to install the package: - -# Using npm - -npm install driver.js - -# Using pnpm - -pnpm install driver.js - -# Using yarn - -yarn add driver.js -Alternatively, you can use CDN and include the script in your HTML file: - - - -Start Using -Once installed, you can import the package in your project. The following example shows how to highlight an element: - -import { driver } from "driver.js"; -import "driver.js/dist/driver.css"; - -const driverObj = driver(); -driverObj.highlight({ -element: "#some-element", -popover: { -title: "Title", -description: "Description" -} -}); -Note on CDN -If you are using the CDN, you will have to use the package from the window object: - -const driver = window.driver.js.driver; - -const driverObj = driver(); - -driverObj.highlight({ -element: "#some-element", -popover: { -title: "Title", -description: "Description" -} -}); -Continue reading the Getting Started guide to learn more about the package. - -Basic Usage -Once installed, you can import and start using the library. There are several different configuration options available to customize the library. You can find more details about the options in the configuration section. Given below are the basic steps to get started. - -Here is a simple example of how to create a tour with multiple steps. - -Basic Tour Example - -import { driver } from "driver.js"; -import "driver.js/dist/driver.css"; - -const driverObj = driver({ -showProgress: true, -steps: [ -{ element: '.page-header', popover: { title: 'Title', description: 'Description' } }, -{ element: '.top-nav', popover: { title: 'Title', description: 'Description' } }, -{ element: '.sidebar', popover: { title: 'Title', description: 'Description' } }, -{ element: '.footer', popover: { title: 'Title', description: 'Description' } }, -] -}); - -driverObj.drive(); -Show me an Example -You can pass a single step configuration to the highlight method to highlight a single element. Given below is a simple example of how to highlight a single element. - -Highlighting a simple Element - -import { driver } from "driver.js"; -import "driver.js/dist/driver.css"; - -const driverObj = driver(); -driverObj.highlight({ -element: '#some-element', -popover: { -title: 'Title for the Popover', -description: 'Description for it', -}, -}); -Show me an Example -The same configuration passed to the highlight method can be used to create a tour. Given below is a simple example of how to create a tour with a single step. - -Examples above show the basic usage of the library. Find more details about the configuration options in the configuration section and the examples in the examples section. - -Configuration -Driver.js is built to be highly configurable. You can configure the driver globally, or per step. You can also configure the driver on the fly, while it’s running. - -Driver.js is written in TypeScript. Configuration options are mostly self-explanatory. Also, if you’re using an IDE like WebStorm or VSCode, you’ll get autocomplete and documentation for all the configuration options. - -Driver Configuration -You can configure the driver globally by passing the configuration object to the driver call or by using the setConfig method. Given below are some of the available configuration options. - -type Config = { -// Array of steps to highlight. You should pass -// this when you want to setup a product tour. -steps?: DriveStep[]; - -// Whether to animate the product tour. (default: true) -animate?: boolean; -// Overlay color. (default: black) -// This is useful when you have a dark background -// and want to highlight elements with a light -// background color. -overlayColor?: string; -// Whether to smooth scroll to the highlighted element. (default: false) -smoothScroll?: boolean; -// Whether to allow closing the popover by clicking on the backdrop. (default: true) -allowClose?: boolean; -// Opacity of the backdrop. (default: 0.5) -overlayOpacity?: number; -// Distance between the highlighted element and the cutout. (default: 10) -stagePadding?: number; -// Radius of the cutout around the highlighted element. (default: 5) -stageRadius?: number; - -// Whether to allow keyboard navigation. (default: true) -allowKeyboardControl?: boolean; - -// Whether to disable interaction with the highlighted element. (default: false) -// Can be configured at the step level as well -disableActiveInteraction?: boolean; - -// If you want to add custom class to the popover -popoverClass?: string; -// Distance between the popover and the highlighted element. (default: 10) -popoverOffset?: number; -// Array of buttons to show in the popover. Defaults to ["next", "previous", "close"] -// for product tours and [] for single element highlighting. -showButtons?: AllowedButtons[]; -// Array of buttons to disable. This is useful when you want to show some of the -// buttons, but disable some of them. -disableButtons?: AllowedButtons[]; - -// Whether to show the progress text in popover. (default: false) -showProgress?: boolean; -// Template for the progress text. You can use the following placeholders in the template: -// - {{current}}: The current step number -// - {{total}}: Total number of steps -progressText?: string; - -// Text to show in the buttons. `doneBtnText` -// is used on the last step of a tour. -nextBtnText?: string; -prevBtnText?: string; -doneBtnText?: string; - -// Called after the popover is rendered. -// PopoverDOM is an object with references to -// the popover DOM elements such as buttons -// title, descriptions, body, container etc. -onPopoverRender?: (popover: PopoverDOM, options: { config: Config; state: State, driver: Driver }) => void; - -// Hooks to run before and after highlighting -// each step. Each hook receives the following -// parameters: -// - element: The target DOM element of the step -// - step: The step object configured for the step -// - options.config: The current configuration options -// - options.state: The current state of the driver -// - options.driver: Current driver object -onHighlightStarted?: (element?: Element, step: DriveStep, options: { config: Config; state: State, driver: Driver }) => void; -onHighlighted?: (element?: Element, step: DriveStep, options: { config: Config; state: State, driver: Driver }) => void; -onDeselected?: (element?: Element, step: DriveStep, options: { config: Config; state: State, driver: Driver }) => void; - -// Hooks to run before and after the driver -// is destroyed. Each hook receives -// the following parameters: -// - element: Currently active element -// - step: The step object configured for the currently active -// - options.config: The current configuration options -// - options.state: The current state of the driver -// - options.driver: Current driver object -onDestroyStarted?: (element?: Element, step: DriveStep, options: { config: Config; state: State, driver: Driver }) => void; -onDestroyed?: (element?: Element, step: DriveStep, options: { config: Config; state: State, driver: Driver }) => void; - -// Hooks to run on button clicks. Each hook receives -// the following parameters: -// - element: The current DOM element of the step -// - step: The step object configured for the step -// - options.config: The current configuration options -// - options.state: The current state of the driver -// - options.driver: Current driver object -onNextClick?: (element?: Element, step: DriveStep, options: { config: Config; state: State, driver: Driver }) => void; -onPrevClick?: (element?: Element, step: DriveStep, options: { config: Config; state: State, driver: Driver }) => void; -onCloseClick?: (element?: Element, step: DriveStep, options: { config: Config; state: State, driver: Driver }) => void; -}; -Note: By overriding onNextClick, and onPrevClick hooks you control the navigation of the driver. This means that user won’t be able to navigate using the buttons and you will have to either call driverObj.moveNext() or driverObj.movePrevious() to navigate to the next/previous step. - -You can use this to implement custom logic for navigating between steps. This is also useful when you are dealing with dynamic content and want to highlight the next/previous element based on some logic. - -onNextClick and onPrevClick hooks can be configured at the step level as well. When configured at the driver level, you control the navigation for all the steps. When configured at the step level, you control the navigation for that particular step only. - -Popover Configuration -The popover is the main UI element of Driver.js. It’s the element that highlights the target element, and shows the step content. You can configure the popover globally, or per step. Given below are some of the available configuration options. - -type Popover = { -// Title and descriptions shown in the popover. -// You can use HTML in these. Also, you can -// omit one of these to show only the other. -title?: string; -description?: string; - -// The position and alignment of the popover -// relative to the target element. -side?: "top" | "right" | "bottom" | "left"; -align?: "start" | "center" | "end"; - -// Array of buttons to show in the popover. -// When highlighting a single element, there -// are no buttons by default. When showing -// a tour, the default buttons are "next", -// "previous" and "close". -showButtons?: ("next" | "previous" | "close")[]; -// An array of buttons to disable. This is -// useful when you want to show some of the -// buttons, but disable some of them. -disableButtons?: ("next" | "previous" | "close")[]; - -// Text to show in the buttons. `doneBtnText` -// is used on the last step of a tour. -nextBtnText?: string; -prevBtnText?: string; -doneBtnText?: string; - -// Whether to show the progress text in popover. -showProgress?: boolean; -// Template for the progress text. You can use -// the following placeholders in the template: -// - {{current}}: The current step number -// - {{total}}: Total number of steps -// Defaults to following if `showProgress` is true: -// - "{{current}} of {{total}}" -progressText?: string; - -// Custom class to add to the popover element. -// This can be used to style the popover. -popoverClass?: string; - -// Hook to run after the popover is rendered. -// You can modify the popover element here. -// Parameter is an object with references to -// the popover DOM elements such as buttons -// title, descriptions, body, etc. -onPopoverRender?: (popover: PopoverDOM, options: { config: Config; state: State, driver: Driver }) => void; - -// Callbacks for button clicks. You can use -// these to add custom behavior to the buttons. -// Each callback receives the following parameters: -// - element: The current DOM element of the step -// - step: The step object configured for the step -// - options.config: The current configuration options -// - options.state: The current state of the driver -// - options.driver: Current driver object -onNextClick?: (element?: Element, step: DriveStep, options: { config: Config; state: State, driver: Driver }) => void -onPrevClick?: (element?: Element, step: DriveStep, options: { config: Config; state: State, driver: Driver }) => void -onCloseClick?: (element?: Element, step: DriveStep, options: { config: Config; state: State, driver: Driver }) => void -} -Drive Step Configuration -Drive step is the configuration object passed to the highlight method or the steps array of the drive method. You can configure the popover and the target element for each step. Given below are some of the available configuration options. - -type DriveStep = { -// The target element to highlight. -// This can be a DOM element, or a CSS selector. -// If this is a selector, the first matching -// element will be highlighted. -element: Element | string; - -// The popover configuration for this step. -// Look at the Popover Configuration section -popover?: Popover; - -// Whether to disable interaction with the highlighted element. (default: false) -disableActiveInteraction?: boolean; - -// Callback when the current step is deselected, -// about to be highlighted or highlighted. -// Each callback receives the following parameters: -// - element: The current DOM element of the step -// - step: The step object configured for the step -// - options.config: The current configuration options -// - options.state: The current state of the driver -// - options.driver: Current driver object -onDeselected?: (element?: Element, step: DriveStep, options: { config: Config; state: State, driver: Driver }) => void; -onHighlightStarted?: (element?: Element, step: DriveStep, options: { config: Config; state: State, driver: Driver }) => void; -onHighlighted?: (element?: Element, step: DriveStep, options: { config: Config; state: State, driver: Driver }) => void; -} -State -You can access the current state of the driver by calling the getState method. It’s also passed to the hooks and callbacks. - -type State = { -// Whether the driver is currently active or not -isInitialized?: boolean; - -// Index of the currently active step if using -// as a product tour and have configured the -// steps array. -activeIndex?: number; -// DOM element of the currently active step -activeElement?: Element; -// Step object of the currently active step -activeStep?: DriveStep; - -// DOM element that was previously active -previousElement?: Element; -// Step object of the previously active step -previousStep?: DriveStep; - -// DOM elements for the popover i.e. including -// container, title, description, buttons etc. -popover?: PopoverDOM; -} - -Here is the list of methods provided by driver when you initialize it. - -Note: We have omitted the configuration options for brevity. Please look at the configuration section for the options. Links are provided in the description below. - -import { driver } from "driver.js"; -import "driver.js/dist/driver.css"; - -// Look at the configuration section for the options -// https://driverjs.com/docs/configuration#driver-configuration -const driverObj = driver({ /_ ... _/ }); - -// -------------------------------------------------- -// driverObj is an object with the following methods -// -------------------------------------------------- - -// Start the tour using `steps` given in the configuration -driverObj.drive(); // Starts at step 0 -driverObj.drive(4); // Starts at step 4 - -driverObj.moveNext(); // Move to the next step -driverObj.movePrevious(); // Move to the previous step -driverObj.moveTo(4); // Move to the step 4 -driverObj.hasNextStep(); // Is there a next step -driverObj.hasPreviousStep() // Is there a previous step - -driverObj.isFirstStep(); // Is the current step the first step -driverObj.isLastStep(); // Is the current step the last step - -driverObj.getActiveIndex(); // Gets the active step index - -driverObj.getActiveStep(); // Gets the active step configuration -driverObj.getPreviousStep(); // Gets the previous step configuration -driverObj.getActiveElement(); // Gets the active HTML element -driverObj.getPreviousElement(); // Gets the previous HTML element - -// Is the tour or highlight currently active -driverObj.isActive(); - -// Recalculate and redraw the highlight -driverObj.refresh(); - -// Look at the configuration section for configuration options -// https://driverjs.com/docs/configuration#driver-configuration -driverObj.getConfig(); -driverObj.setConfig({ /_ ... _/ }); - -driverObj.setSteps([ /* ... */ ]); // Set the steps - -// Look at the state section of configuration for format of the state -// https://driverjs.com/docs/configuration#state -driverObj.getState(); - -// Look at the DriveStep section of configuration for format of the step -// https://driverjs.com/docs/configuration/#drive-step-configuration -driverObj.highlight({ /_ ... _/ }); // Highlight an element - -driverObj.destroy(); // Destroy the tour - -Theming -You can customize the look and feel of the driver by adding custom class to popover or applying CSS to different classes used by driver.js. - -Styling Popover -You can set the popoverClass option globally in the driver configuration or at the step level to apply custom class to the popover and then use CSS to apply styles. - -const driverObj = driver({ -popoverClass: 'my-custom-popover-class' -}); - -// or you can also have different classes for different steps -const driverObj2 = driver({ -steps: [ -{ -element: '#some-element', -popover: { -title: 'Title', -description: 'Description', -popoverClass: 'my-custom-popover-class' -} -} -], -}) -Here is the list of classes applied to the popover which you can use in conjunction with popoverClass option to apply custom styles on the popover. - -/_ Class assigned to popover wrapper _/ -.driver-popover {} - -/_ Arrow pointing towards the highlighted element _/ -.driver-popover-arrow {} - -/_ Title and description _/ -.driver-popover-title {} -.driver-popover-description {} - -/_ Close button displayed on the top right corner _/ -.driver-popover-close-btn {} - -/_ Footer of the popover displaying progress and navigation buttons _/ -.driver-popover-footer {} -.driver-popover-progress-text {} -.driver-popover-prev-btn {} -.driver-popover-next-btn {} -Visit the example page for an example that modifies the popover styles. - -Modifying Popover DOM -Alternatively, you can also use the onPopoverRender hook to modify the popover DOM before it is displayed. The hook is called with the popover DOM as the first argument. - -type PopoverDOM = { -wrapper: HTMLElement; -arrow: HTMLElement; -title: HTMLElement; -description: HTMLElement; -footer: HTMLElement; -progress: HTMLElement; -previousButton: HTMLElement; -nextButton: HTMLElement; -closeButton: HTMLElement; -footerButtons: HTMLElement; -}; - -onPopoverRender?: (popover: PopoverDOM, opts: { config: Config; state: State }) => void; -Styling Page -Following classes are applied to the page when the driver is active. - -/_ Applied to the `body` when the driver: _/ -.driver-active {} /_ is active _/ -.driver-fade {} /_ is animated _/ -.driver-simple {} /_ is not animated _/ -Following classes are applied to the overlay i.e. the lightbox displayed over the page. - -.driver-overlay {} -Styling Highlighted Element -Whenever an element is highlighted, the following classes are applied to it. - -.driver-active-element {} - -Animated Tour -The following example shows how to create a simple tour with a few steps. Click the button below the code sample to see the tour in action. -Basic Animated Tour - -import { driver } from "driver.js"; -import "driver.js/dist/driver.css"; - -const driverObj = driver({ -showProgress: true, -steps: [ -{ element: '#tour-example', popover: { title: 'Animated Tour Example', description: 'Here is the code example showing animated tour. Let\'s walk you through it.', side: "left", align: 'start' }}, -{ element: 'code .line:nth-child(1)', popover: { title: 'Import the Library', description: 'It works the same in vanilla JavaScript as well as frameworks.', side: "bottom", align: 'start' }}, -{ element: 'code .line:nth-child(2)', popover: { title: 'Importing CSS', description: 'Import the CSS which gives you the default styling for popover and overlay.', side: "bottom", align: 'start' }}, -{ element: 'code .line:nth-child(4) span:nth-child(7)', popover: { title: 'Create Driver', description: 'Simply call the driver function to create a driver.js instance', side: "left", align: 'start' }}, -{ element: 'code .line:nth-child(18)', popover: { title: 'Start Tour', description: 'Call the drive method to start the tour and your tour will be started.', side: "top", align: 'start' }}, -{ element: 'a[href="/docs/configuration"]', popover: { title: 'More Configuration', description: 'Look at this page for all the configuration options you can pass.', side: "right", align: 'start' }}, -{ popover: { title: 'Happy Coding', description: 'And that is all, go ahead and start adding tours to your applications.' } } -] -}); - -driverObj.drive(); - -Styling Popover -You can either use the default class names and override the styles or you can pass a custom class name to the popoverClass option either globally or per step. - -Alternatively, if want to modify the Popover DOM, you can use the onPopoverRender callback to get the popover DOM element and do whatever you want with it before popover is rendered. - -We have added a few examples below but have a look at the theming section for detailed guide including class names to target etc. - -Using CSS - -import { driver } from "driver.js"; -import "driver.js/dist/driver.css"; - -const driverObj = driver({ -popoverClass: 'driverjs-theme' -}); - -driverObj.highlight({ -element: '#demo-theme', -popover: { -title: 'Style However You Want', -description: 'You can use the default class names and override the styles or you can pass a custom class name to the popoverClass option either globally or per step.' -} -}); -Driver.js Website Theme -Here is the CSS used for the above example: - -.driver-popover.driverjs-theme { -background-color: #fde047; -color: #000; -} - -.driver-popover.driverjs-theme .driver-popover-title { -font-size: 20px; -} - -.driver-popover.driverjs-theme .driver-popover-title, -.driver-popover.driverjs-theme .driver-popover-description, -.driver-popover.driverjs-theme .driver-popover-progress-text { -color: #000; -} - -.driver-popover.driverjs-theme button { -flex: 1; -text-align: center; -background-color: #000; -color: #ffffff; -border: 2px solid #000; -text-shadow: none; -font-size: 14px; -padding: 5px 8px; -border-radius: 6px; -} - -.driver-popover.driverjs-theme button:hover { -background-color: #000; -color: #ffffff; -} - -.driver-popover.driverjs-theme .driver-popover-navigation-btns { -justify-content: space-between; -gap: 3px; -} - -.driver-popover.driverjs-theme .driver-popover-close-btn { -color: #9b9b9b; -} - -.driver-popover.driverjs-theme .driver-popover-close-btn:hover { -color: #000; -} - -.driver-popover.driverjs-theme .driver-popover-arrow-side-left.driver-popover-arrow { -border-left-color: #fde047; -} - -.driver-popover.driverjs-theme .driver-popover-arrow-side-right.driver-popover-arrow { -border-right-color: #fde047; -} - -.driver-popover.driverjs-theme .driver-popover-arrow-side-top.driver-popover-arrow { -border-top-color: #fde047; -} - -.driver-popover.driverjs-theme .driver-popover-arrow-side-bottom.driver-popover-arrow { -border-bottom-color: #fde047; -} - -Using Hook to Modify - -import { driver } from "driver.js"; -import "driver.js/dist/driver.css"; - -const driverObj = driver({ -// Get full control over the popover rendering. -// Here we are adding a custom button that takes -// the user to the first step. -onPopoverRender: (popover, { config, state }) => { -const firstButton = document.createElement("button"); -firstButton.innerText = "Go to First"; -popover.footerButtons.appendChild(firstButton); - - firstButton.addEventListener("click", () => { - driverObj.drive(0); - }); - -}, -steps: [ -// .. -] -}); - -driverObj.drive(); - -Tour Progress -You can use showProgress option to show the progress of the tour. It is shown in the bottom left corner of the screen. There is also progressText option which can be used to customize the text shown for the progress. - -Please note that showProgress is false by default. Also the default text for progressText is {{current}} of {{total}}. You can use {{current}} and {{total}} in your progressText template to show the current and total steps. - -import { driver } from "driver.js"; -import "driver.js/dist/driver.css"; - -const driverObj = driver({ -showProgress: true, -showButtons: ['next', 'previous'], -steps: [ -{ element: '#tour-example', popover: { title: 'Animated Tour Example', description: 'Here is the code example showing animated tour. Let\'s walk you through it.', side: "left", align: 'start' }}, -{ element: 'code .line:nth-child(1)', popover: { title: 'Import the Library', description: 'It works the same in vanilla JavaScript as well as frameworks.', side: "bottom", align: 'start' }}, -{ element: 'code .line:nth-child(2)', popover: { title: 'Importing CSS', description: 'Import the CSS which gives you the default styling for popover and overlay.', side: "bottom", align: 'start' }}, -{ element: 'code .line:nth-child(4) span:nth-child(7)', popover: { title: 'Create Driver', description: 'Simply call the driver function to create a driver.js instance', side: "left", align: 'start' }}, -{ element: 'code .line:nth-child(16)', popover: { title: 'Start Tour', description: 'Call the drive method to start the tour and your tour will be started.', side: "top", align: 'start' }}, -] -}); - -driverObj.drive(); - -Async Tour -You can also have async steps in your tour. This is useful when you want to load some data from the server and then show the tour. - -Asynchronous Tour - -import { driver } from "driver.js"; -import "driver.js/dist/driver.css"; - -const driverObj = driver({ -showProgress: true, -steps: [ -{ -popover: { -title: 'First Step', -description: 'This is the first step. Next element will be loaded dynamically.' -// By passing onNextClick, you can override the default behavior of the next button. -// This will prevent the driver from moving to the next step automatically. -// You can then manually call driverObj.moveNext() to move to the next step. -onNextClick: () => { -// .. load element dynamically -// .. and then call -driverObj.moveNext(); -}, -}, -}, -{ -element: '.dynamic-el', -popover: { -title: 'Async Element', -description: 'This element is loaded dynamically.' -}, -// onDeselected is called when the element is deselected. -// Here we are simply removing the element from the DOM. -onDeselected: () => { -// .. remove element -document.querySelector(".dynamic-el")?.remove(); -} -}, -{ popover: { title: 'Last Step', description: 'This is the last step.' } } -] - -}); - -driverObj.drive(); -Show me an Example -Note: By overriding onNextClick, and onPrevClick hooks you control the navigation of the driver. This means that user won’t be able to navigate using the buttons and you will have to either call driverObj.moveNext() or driverObj.movePrevious() to navigate to the next/previous step. - -You can use this to implement custom logic for navigating between steps. This is also useful when you are dealing with dynamic content and want to highlight the next/previous element based on some logic. - -onNextClick and onPrevClick hooks can be configured at driver level as well as step level. When configured at the driver level, you control the navigation for all the steps. When configured at the step level, you control the navigation for that particular step only. - -Confirm on Exit -You can use the onDestroyStarted hook to add a confirmation dialog or some other logic when the user tries to exit the tour. In the example below, upon exit we check if there are any tour steps left and ask for confirmation before we exit. - -Confirm on Exit - -import { driver } from "driver.js"; -import "driver.js/dist/driver.css"; - -const driverObj = driver({ -showProgress: true, -steps: [ -{ element: '#confirm-destroy-example', popover: { title: 'Animated Tour Example', description: 'Here is the code example showing animated tour. Let\'s walk you through it.', side: "left", align: 'start' }}, -{ element: 'code .line:nth-child(1)', popover: { title: 'Import the Library', description: 'It works the same in vanilla JavaScript as well as frameworks.', side: "bottom", align: 'start' }}, -{ element: 'code .line:nth-child(2)', popover: { title: 'Importing CSS', description: 'Import the CSS which gives you the default styling for popover and overlay.', side: "bottom", align: 'start' }}, -{ popover: { title: 'Happy Coding', description: 'And that is all, go ahead and start adding tours to your applications.' } } -], -// onDestroyStarted is called when the user tries to exit the tour -onDestroyStarted: () => { -if (!driverObj.hasNextStep() || confirm("Are you sure?")) { -driverObj.destroy(); -} -}, -}); - -driverObj.drive(); -Show me an Example -Note: By overriding the onDestroyStarted hook, you are responsible for calling driverObj.destroy() to exit the tour. - -Prevent Tour Exit -You can also prevent the user from exiting the tour using allowClose option. This option is useful when you want to force the user to complete the tour before they can exit. - -In the example below, you won’t be able to exit the tour until you reach the last step. -Prevent Exit - -import { driver } from "driver.js"; -import "driver.js/dist/driver.css"; - -const driverObj = driver({ -showProgress: true, -allowClose: false, -steps: [ -{ element: '#prevent-exit', popover: { title: 'Animated Tour Example', description: 'Here is the code example showing animated tour. Let\'s walk you through it.', side: "left", align: 'start' }}, -{ element: 'code .line:nth-child(1)', popover: { title: 'Import the Library', description: 'It works the same in vanilla JavaScript as well as frameworks.', side: "bottom", align: 'start' }}, -{ element: 'code .line:nth-child(2)', popover: { title: 'Importing CSS', description: 'Import the CSS which gives you the default styling for popover and overlay.', side: "bottom", align: 'start' }}, -{ popover: { title: 'Happy Coding', description: 'And that is all, go ahead and start adding tours to your applications.' } } -], -}); - -driverObj.drive(); - -Styling Overlay -You can customize the overlay opacity and color using overlayOpacity and overlayColor options to change the look of the overlay. - -Note: In the examples below we have used highlight method to highlight the elements. The same configuration applies to the tour steps as well. - -Overlay Color -Here are some driver.js examples with different overlay colors. - -import { driver } from "driver.js"; -import "driver.js/dist/driver.css"; - -const driverObj = driver({ -overlayColor: 'red' -}); - -driverObj.highlight({ -popover: { -title: 'Pass any RGB Color', -description: 'Here we have set the overlay color to be red. You can pass any RGB color to overlayColor option.' -} -}); - -Popover Position -You can control the popover position using the side and align options. The side option controls the side of the element where the popover will be shown and the align option controls the alignment of the popover with the element. - -Note: Popover is intelligent enough to adjust itself to fit in the viewport. So, if you set side to left and align to start, but the popover doesn’t fit in the viewport, it will automatically adjust itself to fit in the viewport. Consider highlighting and scrolling the browser to the element below to see this in action. - -import { driver } from "driver.js"; -import "driver.js/dist/driver.css"; - -const driverObj = driver(); -driverObj.highlight({ -element: '#left-start', -popover: { -title: 'Animated Tour Example', -description: 'Here is the code example showing animated tour. Let\'s walk you through it.', -side: "left", -align: 'start' -} -}); - -Popover Buttons -You can use the showButtons option to choose which buttons to show in the popover. The default value is ['next', 'previous', 'close']. - -Note: When using the highlight method to highlight a single element, the only button shown is the close button. However, you can use the showButtons option to show other buttons as well. But the buttons won’t do anything. You will have to use the onNextClick and onPreviousClick callbacks to implement the functionality. - -import { driver } from "driver.js"; -import "driver.js/dist/driver.css"; - -const driverObj = driver({ -showButtons: [ -'next', -'previous', -'close' -], -steps: [ -{ -element: '#first-element', -popover: { -title: 'Popover Title', -description: 'Popover Description' -} -}, -{ -element: '#second-element', -popover: { -title: 'Popover Title', -description: 'Popover Description' -} -} -] -}); - -driverObj.drive(); - -Simple Highlight -Product tours is not the only usecase for Driver.js. You can use it to highlight any element on the page and show a popover with a description. This is useful for providing contextual help to the user e.g. help the user fill a form or explain a feature. - -Example below shows how to highlight an element and simply show a popover. - -Highlight Me -Here is the code for above example: - -const driverObj = driver({ -popoverClass: "driverjs-theme", -stagePadding: 4, -}); - -driverObj.highlight({ -element: "#highlight-me", -popover: { -side: "bottom", -title: "This is a title", -description: "This is a description", -} -}) -You can also use it to show a simple modal without highlighting any element. - -Show Popover -Here is the code for above example: - -const driverObj = driver(); - -driverObj.highlight({ -popover: { -description: "Yet another highlight example.", -} -}) -Focus on the input below and see how the popover is shown. - -Enter your Name -Your Education -Your Age -Your Address -Submit -Here is the code for the above example. - -const driverObj = driver({ -popoverClass: "driverjs-theme", -stagePadding: 0, -onDestroyed: () => { -document?.activeElement?.blur(); -} -}); - -const nameEl = document.getElementById("name"); -const educationEl = document.getElementById("education"); -const ageEl = document.getElementById("age"); -const addressEl = document.getElementById("address"); -const formEl = document.querySelector("form"); - -nameEl.addEventListener("focus", () => { -driverObj.highlight({ -element: nameEl, -popover: { -title: "Name", -description: "Enter your name here", -}, -}); -}); - -educationEl.addEventListener("focus", () => { -driverObj.highlight({ -element: educationEl, -popover: { -title: "Education", -description: "Enter your education here", -}, -}); -}); - -ageEl.addEventListener("focus", () => { -driverObj.highlight({ -element: ageEl, -popover: { -title: "Age", -description: "Enter your age here", -}, -}); -}); - -addressEl.addEventListener("focus", () => { -driverObj.highlight({ -element: addressEl, -popover: { -title: "Address", -description: "Enter your address here", -}, -}); -}); - -formEl.addEventListener("blur", () => { -driverObj.destroy(); -}); diff --git a/package-lock.json b/package-lock.json index cebb8f8..16be15b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,14 +8,15 @@ "name": "liftv0", "version": "0.0.0", "dependencies": { + "@types/intro.js": "^5.1.5", "better-auth": "^1.2.3", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.0.4", "compromise": "^14.14.4", "date-fns": "^4.1.0", - "driver.js": "^1.3.5", "framer-motion": "^12.4.1", + "intro.js": "^7.2.0", "lucide-react": "^0.475.0", "react": "^18.3.1", "react-dom": "^18.3.1", @@ -1881,6 +1882,11 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/intro.js": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/@types/intro.js/-/intro.js-5.1.5.tgz", + "integrity": "sha512-TT1d8ayz07svlBcoqh26sNpQaU6bBpdFcCC+IMZHp46NNX2mYAHAVefM3wCmQSd4UWhhObeMjFByw2IaPKOXlw==" + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -2788,11 +2794,6 @@ "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", "license": "MIT" }, - "node_modules/driver.js": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/driver.js/-/driver.js-1.3.5.tgz", - "integrity": "sha512-exkp49hXuujvTOZ3zYgySWRlEAa8/3nA8glYjtuZjmkTdsQITXivBsW1ytyhKQx3WkeYaovlnvVcLbtTaN86kA==" - }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -3545,6 +3546,11 @@ "node": ">=0.8.19" } }, + "node_modules/intro.js": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/intro.js/-/intro.js-7.2.0.tgz", + "integrity": "sha512-qbMfaB70rOXVBceIWNYnYTpVTiZsvQh/MIkfdQbpA9di9VBfj1GigUPfcCv3aOfsbrtPcri8vTLTA4FcEDcHSQ==" + }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", diff --git a/package.json b/package.json index 4ad2c51..026f18c 100644 --- a/package.json +++ b/package.json @@ -11,14 +11,15 @@ "preview": "vite preview --mode production" }, "dependencies": { + "@types/intro.js": "^5.1.5", "better-auth": "^1.2.3", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.0.4", "compromise": "^14.14.4", "date-fns": "^4.1.0", - "driver.js": "^1.3.5", "framer-motion": "^12.4.1", + "intro.js": "^7.2.0", "lucide-react": "^0.475.0", "react": "^18.3.1", "react-dom": "^18.3.1", diff --git a/src/components/ui/tour/AppTour.tsx b/src/components/ui/tour/AppTour.tsx deleted file mode 100644 index a6680db..0000000 --- a/src/components/ui/tour/AppTour.tsx +++ /dev/null @@ -1,123 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import { driver } from 'driver.js'; -import 'driver.js/dist/driver.css'; -import './tour.css'; // Import our custom tour styles - -// Define a type for our tour steps -interface TourStep { - element: string; - popover: { - title: string; - description: string; - side?: "top" | "right" | "bottom" | "left"; - align?: "start" | "center" | "end"; - }; -} - -// Create a predefined tour -const tourSteps: TourStep[] = [ - { - element: '#statementList', - popover: { - title: 'Welcome to Beacons!', - description: 'This is where all your statements will appear.', - side: 'right', - align: 'start' - }, - }, - { - element: '.category-section', - popover: { - title: 'Categories', - description: - 'Statements are organized by categories to help you navigate them easily.', - side: 'right', - align: 'start' - }, - }, - { - element: '.question-card', - popover: { - title: 'Questions', - description: - 'These are questions that will help you create meaningful statements.', - side: 'right', - align: 'start' - }, - }, - { - element: '.add-custom-button', - popover: { - title: 'Add Custom Statements', - description: - 'You can also create your own custom statements from scratch!', - side: 'left', - align: 'start' - }, - }, -]; - -// Create a custom hook to manage the tour -export const useTour = () => { - const [driverObj, setDriverObj] = useState(null); - const [hasSeenTour, setHasSeenTour] = useState(false); - - useEffect(() => { - // Check if user has seen the tour before - const tourSeen = localStorage.getItem('tour_completed'); - if (tourSeen) { - setHasSeenTour(true); - } - - // Initialize driver - const driverInstance = driver({ - showProgress: true, - steps: tourSteps, - nextBtnText: 'Next', - prevBtnText: 'Previous', - doneBtnText: 'Done', - stagePadding: 10, // Add stage padding around highlighted element - popoverOffset: 15, // Distance between popover and element - smoothScroll: true, // Use built-in smooth scrolling - - onDestroyed: () => { - // Mark tour as completed when finished - localStorage.setItem('tour_completed', 'true'); - setHasSeenTour(true); - } - }); - - setDriverObj(driverInstance); - - return () => { - // Cleanup when component unmounts - if (driverInstance) { - driverInstance.destroy(); - } - }; - }, []); - - // Start the tour - const startTour = () => { - if (driverObj) { - driverObj.drive(); - } - }; - - return { startTour, hasSeenTour }; -}; - -// Tour button component -export const TourButton: React.FC = () => { - const { startTour } = useTour(); - - return ( - - ); -}; diff --git a/src/components/ui/tour/TourButton.tsx b/src/components/ui/tour/TourButton.tsx new file mode 100644 index 0000000..9dd58bc --- /dev/null +++ b/src/components/ui/tour/TourButton.tsx @@ -0,0 +1,20 @@ +import 'intro.js/introjs.css'; +import './tour.css'; // Import our custom tour styles +import { useTour } from './useTour'; +import { HelpCircle } from 'lucide-react'; + +// Tour button component +export function TourButton(): JSX.Element { + const { startTour } = useTour(); + + return ( + + ); +} diff --git a/src/components/ui/tour/tour.css b/src/components/ui/tour/tour.css index 5dbfb17..8f8617b 100644 --- a/src/components/ui/tour/tour.css +++ b/src/components/ui/tour/tour.css @@ -1,22 +1,204 @@ -/* Custom styles for driver.js tour */ -.driver-popover { - max-width: 300px; +/* Custom styles for intro.js tour */ +.introjs-tooltip { + max-width: 350px; + min-width: 300px; + z-index: 999999999 !important; + position: relative !important; +} + +/* Force all intro.js elements to be visible at the top layer */ +.introjs-overlay, +.introjs-helperLayer, +.introjs-tooltipReferenceLayer, +.introjs-tooltip, +.introjs-tooltip * { + z-index: 999999990 !important; +} + +/* Style tooltip headers */ +.introjs-tooltip h3 { + margin-top: 0; + color: #333; + font-size: 1.1rem; + font-weight: bold; + margin-bottom: 0.5rem; } /* Style the next button with app's pink color theme */ -.driver-popover-next-btn { - background-color: #ec4899; +.introjs-nextbutton { + background-color: #ec4899 !important; + border-color: #ec4899 !important; + text-shadow: none !important; + color: white !important; +} + +.introjs-nextbutton:hover { + background-color: #db2777 !important; + border-color: #db2777 !important; +} + +/* Style disabled buttons */ +.introjs-button[disabled] { + opacity: 0.5 !important; + cursor: not-allowed !important; +} + +/* Our custom tooltip class */ +.tour-tooltip { + border-radius: 8px !important; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15) !important; +} + +/* Our custom highlight class */ +.tour-highlight { + box-shadow: 0 0 0 5px rgba(236, 72, 153, 0.3) !important; + border-radius: 4px; +} + +/* Interactive element styling */ +.interactive-tour-element { + cursor: pointer !important; + animation: pulse 1.5s infinite !important; + position: relative; + z-index: 10000002 !important; +} + +/* Pulse animation for interactive elements */ +@keyframes pulse { + 0% { + box-shadow: 0 0 0 0 rgba(236, 72, 153, 0.7); + } + 70% { + box-shadow: 0 0 0 10px rgba(236, 72, 153, 0); + } + 100% { + box-shadow: 0 0 0 0 rgba(236, 72, 153, 0); + } +} + +/* Message for interactive steps */ +.interactive-step-message { + font-weight: bold; + color: #ec4899; + margin-top: 0.75rem; + padding: 4px 8px; + border: 1px dashed #ec4899; + border-radius: 4px; + background-color: rgba(236, 72, 153, 0.05); +} + +/* Special styling for wizard steps */ +.subject-tile.interactive-tour-element, +.verb-button.interactive-tour-element { + animation: shimmer 2s infinite !important; + position: relative; + cursor: pointer !important; +} + +@keyframes shimmer { + 0% { + box-shadow: 0 0 0 0 rgba(236, 72, 153, 0.3); + } + 70% { + box-shadow: 0 0 0 8px rgba(236, 72, 153, 0); + } + 100% { + box-shadow: 0 0 0 0 rgba(236, 72, 153, 0); + } +} + +/* Fix intro.js tooltip arrow colors */ +.introjs-arrow.top { + border-bottom-color: #fff !important; +} +.introjs-arrow.right { + border-left-color: #fff !important; +} +.introjs-arrow.bottom { + border-top-color: #fff !important; +} +.introjs-arrow.left { + border-right-color: #fff !important; +} + +/* Modal positioning and z-index fixes */ +.ReactModal__Content.intro-highlighted-modal, +.ReactModal__Overlay.intro-highlighted-modal { + position: absolute !important; + z-index: 10000001 !important; +} + +/* Ensure tooltips are visible over modals - using extremely high z-index values */ +.introjs-tooltipReferenceLayer { + z-index: 999999999 !important; +} + +.introjs-tooltip { + z-index: 999999999 !important; +} + +.introjs-helperLayer { + z-index: 999999998 !important; +} + +.introjs-overlay { + z-index: 999999990 !important; +} + +/* Additional positioning fixes for tooltips */ +.introjs-fixParent { + z-index: 999999995 !important; + position: relative !important; +} + +/* Disable all interactions during tour except highlighted elements */ +body.tour-interactive-step *:not(.interactive-tour-element):not(.interactive-tour-element *):not(.introjs-tooltip):not(.introjs-tooltip *) { + pointer-events: none !important; +} + +/* Re-enable interactions for tour components */ +body.tour-interactive-step .introjs-tooltip, +body.tour-interactive-step .introjs-tooltip *, +body.tour-interactive-step .interactive-tour-element, +body.tour-interactive-step .interactive-tour-element *, +body.tour-interactive-step .ReactModal__Content.intro-highlighted-modal, +body.tour-interactive-step .ReactModal__Content.intro-highlighted-modal * { + pointer-events: auto !important; +} + +/* Make wizard buttons interactive for modal steps */ +body.tour-interactive-step .step-container button, +body.tour-interactive-step button[type="submit"], +body.tour-interactive-step .subject-tile, +body.tour-interactive-step .verb-button { + pointer-events: auto !important; + z-index: 10000001 !important; + position: relative !important; } -.driver-popover-next-btn:hover { - background-color: #db2777; +/* Custom overlay for intro.js - a bit more subtle */ +.introjs-overlay { + background: rgba(0, 0, 0, 0.6) !important; } -/* Fix for z-index to ensure popover is above sticky header */ -.driver-active { - z-index: 1000; +/* Fix focus outline issues */ +.introjs-helperLayer { + background-color: transparent !important; + border: none !important; } -.driver-active .driver-highlighted-element { - z-index: 1001; +/* Improve introjs tooltips for mobile devices */ +@media (max-width: 576px) { + .introjs-tooltip { + min-width: 250px; + max-width: 300px; + } + + .introjs-tooltiptext { + font-size: 14px; + } + + .introjs-button { + padding: 6px 10px !important; + } } \ No newline at end of file diff --git a/src/components/ui/tour/useTour.ts b/src/components/ui/tour/useTour.ts new file mode 100644 index 0000000..ec39e5b --- /dev/null +++ b/src/components/ui/tour/useTour.ts @@ -0,0 +1,508 @@ +import { useState, useEffect, useRef } from 'react'; +import introJs from 'intro.js'; +// Import the intro.js styles +import 'intro.js/introjs.css'; +// Import our custom CSS +import './tour.css'; + +// Define types for intro.js +type IntroJs = ReturnType; + +/** + * Extended IntroJs type with access to private properties + * Using type intersection instead of interface extension to avoid compatibility issues + */ +type IntroJsExt = IntroJs & { + _currentStep?: number; + _options?: { + steps: IntroStep[]; + nextLabel?: string; + prevLabel?: string; + skipLabel?: string; + doneLabel?: string; + hidePrev?: boolean; + hideNext?: boolean; + tooltipPosition?: string; + tooltipClass?: string; + highlightClass?: string; + [key: string]: unknown; + }; +}; + +// We can omit the separate IntroJsOptions interface since we're +// directly using the intro.js library's API + +/** + * Standard intro.js step + */ +interface IntroStep { + element: string | HTMLElement; + intro: string; + position?: 'top' | 'right' | 'bottom' | 'left'; + tooltipClass?: string; + highlightClass?: string; + disableInteraction?: boolean; +} + +/** + * Return type for our useTour hook + */ +export interface TourHookResult { + startTour: () => void; + hasSeenTour: boolean; +} + +/** + * Our custom step options extending IntroStep + */ +interface CustomStepOptions { + // Basic intro.js step properties + element?: string | HTMLElement; + intro: string; + position?: 'top' | 'right' | 'bottom' | 'left'; + tooltipClass?: string; + highlightClass?: string; + disableInteraction?: boolean; + + // Our custom properties + isInteractive?: boolean; + waitForElementChange?: { + selector: string; + observeSubtree?: boolean; + }; + onBeforeChange?: (targetElement: HTMLElement) => boolean | void; + onAfterChange?: (targetElement: HTMLElement) => void; +} + +// Create the predefined tour steps with intro.js format +// Will be populated in the hook +let _introInstance: IntroJs | null = null; + +export const tourSteps: CustomStepOptions[] = [ + { + // Step 01 - Explain the wizard that's already open + intro: + '

Statement Wizard

Welcome to Beacons! You\'re looking at the Statement Wizard, which helps you create statements to share with your employer.

', + position: 'bottom', + }, + { + // Step 02 - Subject selection + element: '.subject-tiles', + intro: + '

Step 1: Choose a Subject

Start by selecting the subject of your statement. This is often "I" or another perspective like "My team" or "The company".

', + position: 'right', + }, + { + // Step 03 - Verb selection + element: '.verb-grid', + intro: + '

Step 2: Choose a Verb

Next, select a verb that expresses your feeling or action. The sentiment filters at the top help you find positive, neutral, or negative verbs.

', + position: 'bottom', + }, + { + // Step 04 - Complete Statement + intro: + '

Step 3: Complete Your Statement

Then you\'ll type what you\'re feeling or experiencing to complete your statement.

', + position: 'bottom', + }, + { + // Step 05 - Choose Category + intro: + '

Step 4: Choose a Category

Select a category to help organize your feedback by topic.

', + position: 'bottom', + }, + { + // Step 06 - Privacy Settings + intro: + '

Final Step: Privacy Settings

Finally, choose whether to share this statement with your employer. Public statements will be included when you send insights to your manager.

', + position: 'bottom', + } +]; + +// Create a custom hook to manage the tour +export function useTour(): TourHookResult { + const [introInstance, setIntroInstance] = useState(null); + const [hasSeenTour, setHasSeenTour] = useState(false); + const observersRef = useRef([]); + const currentStepRef = useRef(0); + + // Helper to clean up observers + const cleanupObservers = () => { + observersRef.current.forEach((observer) => observer.disconnect()); + observersRef.current = []; + }; + + // Listen for custom advance event + useEffect(() => { + const handleAdvance = () => { + if (introInstance) { + introInstance.nextStep(); + } + }; + + // Force tooltips to be visible at high z-index every 100ms + const forceTooltipVisibility = () => { + const tooltips = document.querySelectorAll('.introjs-tooltipReferenceLayer, .introjs-tooltip, .introjs-helperLayer'); + if (tooltips.length > 0) { + tooltips.forEach(el => { + const element = el as HTMLElement; + element.style.zIndex = '999999999'; + }); + } + }; + + const intervalId = setInterval(forceTooltipVisibility, 100); + window.addEventListener('INTRO_ADVANCE', handleAdvance); + + return () => { + window.removeEventListener('INTRO_ADVANCE', handleAdvance); + clearInterval(intervalId); + }; + }, [introInstance]); + + useEffect(() => { + // Check if user has seen the tour before + const tourSeen = localStorage.getItem('tour_completed'); + if (tourSeen) { + setHasSeenTour(true); + } + + // Initialize intro.js instance + const intro = introJs(); + + // Function to convert our custom steps to standard IntroStep + const convertToIntroStep = (step: CustomStepOptions): IntroStep => { + // Pick only the properties that IntroStep expects + const { + element, + intro, + position, + disableInteraction, + tooltipClass, + highlightClass, + } = step; + + return { + element: element || 'body', // Ensure element is always defined + intro, + position, + disableInteraction, + tooltipClass, + highlightClass, + }; + }; + + // Prepare steps for intro.js - making sure they match the format it expects + const preparedSteps = tourSteps.map(convertToIntroStep); + + // Configure intro.js with clean steps + intro.setOptions({ + steps: preparedSteps, // Already typed as IntroStep[] + showProgress: true, + showBullets: true, + exitOnOverlayClick: false, + exitOnEsc: false, + showStepNumbers: false, + disableInteraction: true, // Default is to disable interaction + scrollToElement: true, + tooltipClass: 'tour-tooltip', + highlightClass: 'tour-highlight', + nextLabel: 'Next', + prevLabel: 'Back', + doneLabel: 'Done', + }); + + // Handle before-change event + intro.onbeforechange(function (targetElement: HTMLElement) { + if (!targetElement) return true; // Return true to continue to the next step + + const currentIndex = (intro as IntroJsExt)._currentStep || 0; + currentStepRef.current = currentIndex; + const step = tourSteps[currentIndex] as CustomStepOptions; + + console.log('Step change', currentIndex, targetElement); + + // Force z-index to be high on intro.js elements - apply this for ALL steps + setTimeout(() => { + // Force tooltip visibility with extremely high z-indices + document.querySelectorAll('.introjs-tooltipReferenceLayer, .introjs-tooltip, .introjs-helperLayer, .introjs-overlay') + .forEach(el => { + const element = el as HTMLElement; + if (element.classList.contains('introjs-tooltip')) { + element.style.zIndex = '999999999'; + } else if (element.classList.contains('introjs-helperLayer')) { + element.style.zIndex = '999999998'; + } else if (element.classList.contains('introjs-overlay')) { + element.style.zIndex = '999999990'; + } else { + element.style.zIndex = '999999999'; + } + }); + + // Fix any ReactModals + const reactModals = document.querySelectorAll('.ReactModal__Overlay, .ReactModal__Content'); + reactModals.forEach(modal => { + modal.classList.add('intro-highlighted-modal'); + }); + }, 10); + + // Clean up any existing observers + cleanupObservers(); + + // Call custom beforeChange handler if defined + if (step.onBeforeChange) { + const result = step.onBeforeChange(targetElement); + if (result === false) { + // If step has waitForElementChange, set up observer + if (step.waitForElementChange) { + const { selector, observeSubtree = true } = + step.waitForElementChange; + + const observer = new MutationObserver(() => { + const elementExists = document.querySelector(selector); + if (elementExists) { + document.body.classList.remove('tour-interactive-step'); + targetElement.classList.remove('interactive-tour-element'); + observer.disconnect(); + setTimeout( + () => window.dispatchEvent(new CustomEvent('INTRO_ADVANCE')), + 500 + ); + } + }); + + observer.observe(document.body, { + childList: true, + subtree: observeSubtree, + }); + + observersRef.current.push(observer); + } + + // For steps that require interaction, hide the next button + if (step.isInteractive) { + // Delay this slightly to ensure the button is rendered + setTimeout(() => { + const nextButton = document.querySelector('.introjs-nextbutton'); + if (nextButton) { + (nextButton as HTMLElement).style.display = 'none'; + } + }, 50); + } + + return false; // Prevent auto-advancing to the next step + } + } + + return true; // Continue to the next step + }); + + // Handle change event (after a step is shown) + intro.onafterchange(function (targetElement: HTMLElement) { + if (!targetElement) return; + + const currentIndex = (intro as IntroJsExt)._currentStep || 0; + const step = tourSteps[currentIndex] as CustomStepOptions; + + // Call custom afterChange handler if defined + if (step.onAfterChange) { + step.onAfterChange(targetElement); + } + }); + + // Handle complete event + intro.oncomplete(() => { + // Mark tour as completed + localStorage.setItem('tour_completed', 'true'); + setHasSeenTour(true); + cleanupObservers(); + + // Remove any leftover classes + document.body.classList.remove('tour-interactive-step'); + const interactiveElements = document.querySelectorAll( + '.interactive-tour-element' + ); + interactiveElements.forEach((el) => + el.classList.remove('interactive-tour-element') + ); + + const modalElements = document.querySelectorAll( + '.intro-highlighted-modal' + ); + modalElements.forEach((el) => + el.classList.remove('intro-highlighted-modal') + ); + }); + + // Handle exit event + intro.onexit(() => { + // Clean up when tour is exited + cleanupObservers(); + + // Remove any leftover classes + document.body.classList.remove('tour-interactive-step'); + const interactiveElements = document.querySelectorAll( + '.interactive-tour-element' + ); + interactiveElements.forEach((el) => + el.classList.remove('interactive-tour-element') + ); + + const modalElements = document.querySelectorAll( + '.intro-highlighted-modal' + ); + modalElements.forEach((el) => + el.classList.remove('intro-highlighted-modal') + ); + }); + + setIntroInstance(intro); + // Store for access from steps + _introInstance = intro; + + return () => { + // Cleanup when component unmounts + cleanupObservers(); + intro.exit(true); // Force exit + }; + }, []); + + // Start the tour + const startTour = () => { + if (introInstance) { + // Start at the top of the page + window.scrollTo({ top: 0, behavior: 'auto' }); + + // Pre-fix any tooltips that might already be present + document.querySelectorAll('.ReactModal__Overlay, .ReactModal__Content').forEach(el => { + (el as HTMLElement).style.zIndex = '999999980'; + }); + + // Pre-open the wizard to demonstrate (without waiting for tour steps) + const tryOpenWizard = () => { + console.log('Pre-tour attempt to open wizard'); + + // Find the clickable part of the question card - the outer div with cursor-pointer + const clickableCard = document.querySelector('.question-card .cursor-pointer'); + + // Fallback to any question card if the specific selector doesn't work + const anyQuestionCard = document.querySelector('.question-card'); + + // Try the most specific target first + if (clickableCard) { + console.log('Found clickable question card div, clicking it'); + + // Create and dispatch mouse events for more reliable triggering + const clickEvent = new MouseEvent('click', { + bubbles: true, + cancelable: true, + view: window + }); + clickableCard.dispatchEvent(clickEvent); + + // Also try direct click as fallback + try { + (clickableCard as HTMLElement).click(); + } catch (e) { + console.error('Error with direct click on clickable div', e); + } + + // Wait for wizard to open, then start tour + setTimeout(() => { + // Check if wizard opened + const wizardModal = document.querySelector('.ReactModal__Content'); + if (wizardModal) { + console.log('Wizard opened successfully, starting tour'); + } else { + console.log('Wizard did not open yet, trying anyway'); + } + + // Now start the tour + introInstance.start(); + + // Force tooltips to be visible immediately + setTimeout(() => { + document.querySelectorAll('.introjs-tooltipReferenceLayer, .introjs-tooltip, .introjs-helperLayer, .introjs-overlay') + .forEach(el => { + const element = el as HTMLElement; + element.style.zIndex = '999999999'; + }); + }, 100); + }, 800); // Longer delay to ensure wizard opens + + } else if (anyQuestionCard) { + // Fallback to the whole question card + console.log('Fallback: clicking whole question card'); + + // Try to find the actual clickable element within the card + const allDivsInCard = anyQuestionCard.querySelectorAll('div'); + let clicked = false; + + // Try clicking each div in the card until we find one that works + allDivsInCard.forEach(div => { + if (!clicked) { + try { + const clickEvent = new MouseEvent('click', { + bubbles: true, + cancelable: true, + view: window + }); + div.dispatchEvent(clickEvent); + (div as HTMLElement).click(); + console.log('Tried clicking div in card', div); + clicked = true; + } catch (e) { + // Continue to next div + } + } + }); + + // If we couldn't find a specific element, try the whole card + if (!clicked) { + try { + const clickEvent = new MouseEvent('click', { + bubbles: true, + cancelable: true, + view: window + }); + anyQuestionCard.dispatchEvent(clickEvent); + (anyQuestionCard as HTMLElement).click(); + } catch (e) { + console.error('Error clicking question card', e); + } + } + + // Wait for wizard to open, then start tour + setTimeout(() => { + introInstance.start(); + + setTimeout(() => { + document.querySelectorAll('.introjs-tooltipReferenceLayer, .introjs-tooltip, .introjs-helperLayer, .introjs-overlay') + .forEach(el => { + const element = el as HTMLElement; + element.style.zIndex = '999999999'; + }); + }, 100); + }, 800); + + } else { + console.log('Could not find any question card, starting tour anyway'); + introInstance.start(); + + setTimeout(() => { + document.querySelectorAll('.introjs-tooltipReferenceLayer, .introjs-tooltip, .introjs-helperLayer, .introjs-overlay') + .forEach(el => { + const element = el as HTMLElement; + element.style.zIndex = '999999999'; + }); + }, 100); + } + }; + + // Try to open the wizard first, then start tour + setTimeout(tryOpenWizard, 100); + } + }; + + return { startTour, hasSeenTour }; +} diff --git a/src/data/descriptors.json b/src/data/descriptors.json index 219ca60..e0957fe 100644 --- a/src/data/descriptors.json +++ b/src/data/descriptors.json @@ -1,5 +1,10 @@ { "descriptors": [ + { + "name": "Example", + "description": "Descriptors for the tutorial", + "options": ["Option1", "Option2", "Option3", "Option4"] + }, { "name": "wellbeing", "description": "Descriptors related to the user's mental and emotional state, including stress, energy, and overall wellbeing.", @@ -41,7 +46,7 @@ ] }, { - "name": "colleagueSharing", + "name": "Colleague_Sharing", "description": "Descriptors regarding the type and level of information the user is comfortable sharing with colleagues.", "options": [ "detailed sharing", @@ -51,7 +56,7 @@ ] }, { - "name": "employerSupport", + "name": "employer_Support", "description": "Descriptors regarding the user's support and communication preferences from the employer.", "options": [ "formal check-ins", @@ -61,7 +66,7 @@ ] }, { - "name": "personalIntroduction", + "name": "personal_Introduction", "description": "Descriptors for personal introduction details that capture background, hobbies, ambitions, and other personal insights.", "options": [ "family background", diff --git a/src/data/setQuestions.json b/src/data/setQuestions.json index 1e91f6b..c622080 100644 --- a/src/data/setQuestions.json +++ b/src/data/setQuestions.json @@ -321,7 +321,7 @@ }, { "id": "q12", - "category": "colleague_Sharing", + "category": "Colleague_Sharing", "mainQuestion": "Share the information about your work needs that would be helpful for your colleagues.", "steps": { "subject": { diff --git a/src/features/statements/components/StatementList.tsx b/src/features/statements/components/StatementList.tsx index ef206eb..e7dd11b 100644 --- a/src/features/statements/components/StatementList.tsx +++ b/src/features/statements/components/StatementList.tsx @@ -540,7 +540,7 @@ const StatementList: React.FC = ({ return ( <> -
+
{/* Regular categories */} {definedCategories.map((cat) => renderCategorySection(cat.id, cat.name) diff --git a/src/layouts/components/Header.tsx b/src/layouts/components/Header.tsx index e63f1d1..19b1834 100644 --- a/src/layouts/components/Header.tsx +++ b/src/layouts/components/Header.tsx @@ -3,7 +3,7 @@ import React, { useState } from 'react'; import { useEntries } from '../../features/statements/hooks/useEntries'; import SmallCircularQuestionCounter from '../../components/ui/questionCounter/smallCircularQuestionCounter'; import UserDataModal from '../../components/modals/UserDataModal'; -import { TourButton } from '../../components/ui/tour/AppTour'; +import { TourButton } from '../../components/ui/tour/TourButton'; // import { Tooltip, TooltipTrigger, TooltipContent } from '../../components/ui/better-tooltip'; const Header: React.FC = () => { @@ -37,7 +37,7 @@ const Header: React.FC = () => { {/* Right section: Login button and Tour button */}
{data.username ? ( -
+
and . + +
+

+ By signing in, you agree to our{' '} + {' '} + and{' '} + + .

- We collect and process your data to provide the Beacons service. You control what information is shared with your employer. + We collect and process your data to provide the Beacons service. + You control what information is shared with your employer.

- ) : ( <> -

Complete Your Profile

+

+ Complete Your Profile +

- Please enter your name and, optionally, your line manager's name to continue. + Please enter your name and, optionally, your line manager's name + to continue.

-
- +
-
{state.user?.email && ( -
-
+
+
Email Address
-
+
{state.user.email}
-

+

You signed in with this email address

diff --git a/src/index.css b/src/index.css index 00e759a..848bb5c 100644 --- a/src/index.css +++ b/src/index.css @@ -99,6 +99,6 @@ @apply border-border; } body { - @apply bg-background text-foreground font-sans; + @apply bg-background text-foreground font-sans bg-gray-50; } } diff --git a/src/layouts/components/Header.tsx b/src/layouts/components/Header.tsx index 19b1834..64af347 100644 --- a/src/layouts/components/Header.tsx +++ b/src/layouts/components/Header.tsx @@ -3,7 +3,7 @@ import React, { useState } from 'react'; import { useEntries } from '../../features/statements/hooks/useEntries'; import SmallCircularQuestionCounter from '../../components/ui/questionCounter/smallCircularQuestionCounter'; import UserDataModal from '../../components/modals/UserDataModal'; -import { TourButton } from '../../components/ui/tour/TourButton'; +// import { TourButton } from '../../components/ui/tour/TourButton'; // import { Tooltip, TooltipTrigger, TooltipContent } from '../../components/ui/better-tooltip'; const Header: React.FC = () => { @@ -20,20 +20,16 @@ const Header: React.FC = () => { }; return ( -
-
+
+
{/* Simplified header layout */} -
- {/* Left section: Logo */} +
+ {/* Left section: Logo and Title */}
Logo +

Beacons

- {/* Center section: Title */} -

- Beacons -

- {/* Right section: Login button and Tour button */}
{data.username ? ( @@ -47,7 +43,7 @@ const Header: React.FC = () => { - + {/* */}
) : (
diff --git a/src/layouts/components/MainPage.tsx b/src/layouts/components/MainPage.tsx index 1e51b26..ae80028 100644 --- a/src/layouts/components/MainPage.tsx +++ b/src/layouts/components/MainPage.tsx @@ -1,6 +1,6 @@ 'use client'; -import React, { useState, useEffect } from 'react'; +import React, { useState } from 'react'; import { Tooltip, TooltipTrigger, @@ -15,7 +15,7 @@ import ShareEmailModal from '../../components/modals/ShareEmailModal'; import PrivacyModal from '../../components/modals/PrivacyModal'; import TermsModal from '../../components/modals/TermsModal'; import TestStatementButton from '../../components/debug/TestButton'; -import { useTour } from '../../components/ui/tour/useTour'; +// import { useTour } from '../../components/ui/tour/useTour'; const MainPage: React.FC = () => { const { data } = useEntries(); @@ -24,22 +24,22 @@ const MainPage: React.FC = () => { const [isShareModalOpen, setIsShareModalOpen] = useState(false); const [isPrivacyModalOpen, setIsPrivacyModalOpen] = useState(false); const [isTermsModalOpen, setIsTermsModalOpen] = useState(false); - + // Get tour functionality - const { startTour, hasSeenTour } = useTour(); - + // const { startTour, hasSeenTour } = useTour(); + // Auto-start tour for new users - useEffect(() => { - // Check if the user is authenticated and has not seen the tour - if (username && !hasSeenTour) { - // Wait for the UI to fully render before starting the tour - const tourTimeout = setTimeout(() => { - startTour(); - }, 1000); - - return () => clearTimeout(tourTimeout); - } - }, [username, hasSeenTour, startTour]); + // useEffect(() => { + // // Check if the user is authenticated and has not seen the tour + // if (username && !hasSeenTour) { + // // Wait for the UI to fully render before starting the tour + // const tourTimeout = setTimeout(() => { + // startTour(); + // }, 1000); + + // return () => clearTimeout(tourTimeout); + // } + // }, [username, hasSeenTour, startTour]); // Determine if email button should be disabled: const hasManagerEmail = managerEmail && managerEmail.trim().length > 0; @@ -66,7 +66,10 @@ const MainPage: React.FC = () => {
{/* Fixed header layout with 1 or 2 rows */}
-

+

{managerName ? `${username} would like to share with ${managerName}` : `${username}'s statements for sharing`} From 462d7a3f8e8554a4f9dcb90330f170daa644591e Mon Sep 17 00:00:00 2001 From: Alexander Date: Sun, 23 Mar 2025 13:46:12 +0000 Subject: [PATCH 06/34] style: adjust sizes for mobile --- .../statements/components/StatementList.tsx | 307 +++++++++++------- src/layouts/components/MainPage.tsx | 4 +- 2 files changed, 188 insertions(+), 123 deletions(-) diff --git a/src/features/statements/components/StatementList.tsx b/src/features/statements/components/StatementList.tsx index e7dd11b..3d69b8e 100644 --- a/src/features/statements/components/StatementList.tsx +++ b/src/features/statements/components/StatementList.tsx @@ -20,12 +20,12 @@ import { Button } from '../../../components/ui/button'; const normalizeCategoryIdForGrouping = (id: string): string => { // Convert to lowercase and handle special cases const normalized = id ? id.toLowerCase() : ''; - + // Handle variations of "uncategorized" if (['uncategorized', 'uncategorised'].includes(normalized)) { return 'uncategorized'; } - + return normalized; }; @@ -33,9 +33,9 @@ const normalizeCategoryIdForGrouping = (id: string): string => { const mapToPredefinedCategoryId = (categoryId: string): string => { const normalized = normalizeCategoryIdForGrouping(categoryId); const predefinedCategory = statementsCategories.categories.find( - c => normalizeCategoryIdForGrouping(c.id) === normalized + (c) => normalizeCategoryIdForGrouping(c.id) === normalized ); - + return predefinedCategory ? predefinedCategory.id : categoryId; }; @@ -52,7 +52,9 @@ const groupQuestionsByCategory = (questions: SetQuestion[]) => { const groupStatementsByCategory = (statements: Entry[]) => { return statements.reduce>((acc, statement) => { // Use the predefined category ID if it exists, otherwise use the normalized ID - const cat = mapToPredefinedCategoryId(statement.category || 'Uncategorized'); + const cat = mapToPredefinedCategoryId( + statement.category || 'Uncategorized' + ); if (!acc[cat]) acc[cat] = []; acc[cat].push(statement); return acc; @@ -64,16 +66,16 @@ interface StatementListProps { onAddCustomStatement?: () => void; } -const StatementList: React.FC = ({ +const StatementList: React.FC = ({ username, - onAddCustomStatement + onAddCustomStatement, }) => { const { data, setData } = useEntries(); const { entries } = data; const [usedPresetQuestions, setUsedPresetQuestions] = useState([]); const { questions, setQuestions } = useQuestions(); - + // Get available preset questions (not used and not in snoozed section) const presetQuestions = questions.filter( (q) => !usedPresetQuestions.includes(q.id) && !q.isSnoozed @@ -100,12 +102,16 @@ const StatementList: React.FC = ({ statement: Entry; editPart: 'subject' | 'verb' | 'object' | 'category' | 'privacy'; } | null>(null); - + // Keep a backup of the original entries when entering edit mode - const [originalEntries, setOriginalEntries] = useState<{[id: string]: Entry}>({}); - + const [originalEntries, setOriginalEntries] = useState<{ + [id: string]: Entry; + }>({}); + // Store original categories to compare when statements are edited - const [originalCategories, setOriginalCategories] = useState<{[id: string]: string}>({}); + const [originalCategories, setOriginalCategories] = useState<{ + [id: string]: string; + }>({}); // Handle toggling the resolved state (archive/unarchive) const handleToggleResolved = (statementId: string) => { @@ -115,23 +121,22 @@ const StatementList: React.FC = ({ setData({ type: 'UPDATE_ENTRY', payload: updated }); updateEntry(updated); }; - - + // Handle toggling the snoozed state for questions const handleToggleQuestionSnooze = (questionId: string) => { // Find the question in the questions array - const questionToToggle = questions.find(q => q.id === questionId); + const questionToToggle = questions.find((q) => q.id === questionId); if (!questionToToggle) return; - + // Create a new array with the updated question - const updatedQuestions = questions.map(q => { + const updatedQuestions = questions.map((q) => { if (q.id === questionId) { if (q.isSnoozed) { // Unsnooze - restore to original category if available return { ...q, isSnoozed: false, - category: q.originalCategory || q.category + category: q.originalCategory || q.category, }; } else { // Snooze - store original category and move to snoozed @@ -139,13 +144,13 @@ const StatementList: React.FC = ({ ...q, isSnoozed: true, originalCategory: q.category, - category: 'snoozed' + category: 'snoozed', }; } } return q; }); - + // Update the questions in context setQuestions(updatedQuestions); }; @@ -171,24 +176,24 @@ const StatementList: React.FC = ({ try { // Call the backend API with the updated entry await updateEntry(updatedEntry); - + // Update the context with the new entry setData({ type: 'UPDATE_ENTRY', payload: updatedEntry }); - + // Remove from originalEntries since we've saved - setOriginalEntries(prev => { - const newEntries = {...prev}; + setOriginalEntries((prev) => { + const newEntries = { ...prev }; delete newEntries[updatedEntry.id]; return newEntries; }); - + // Remove from originalCategories - setOriginalCategories(prev => { - const newCategories = {...prev}; + setOriginalCategories((prev) => { + const newCategories = { ...prev }; delete newCategories[updatedEntry.id]; return newCategories; }); - + // Exit editing mode for this statement setEditingStatementId(null); } catch (error) { @@ -237,21 +242,21 @@ const StatementList: React.FC = ({ // Single edit option: when user clicks "Edit" in the dropdown const handleEditClick = (statementId: string) => { // Store the original entry for possible reversion later - const entryToEdit = entries.find(e => e.id === statementId); + const entryToEdit = entries.find((e) => e.id === statementId); if (entryToEdit) { // Store the full entry for restoring on cancel - setOriginalEntries(prev => ({ + setOriginalEntries((prev) => ({ ...prev, - [statementId]: JSON.parse(JSON.stringify(entryToEdit)) + [statementId]: JSON.parse(JSON.stringify(entryToEdit)), })); - + // Store the original category for change detection - setOriginalCategories(prev => ({ + setOriginalCategories((prev) => ({ ...prev, - [statementId]: entryToEdit.category || '' + [statementId]: entryToEdit.category || '', })); } - + // Enable edit mode setEditingStatementId(statementId); }; @@ -345,44 +350,61 @@ const StatementList: React.FC = ({ }; // State for managing the visibility of the snoozed section - default to expanded - const [isSnoozedQuestionsSectionExpanded, setIsSnoozedQuestionsSectionExpanded] = useState(true); - + const [ + isSnoozedQuestionsSectionExpanded, + setIsSnoozedQuestionsSectionExpanded, + ] = useState(true); + // Move the hook call to the top level const { categoryCounts } = useAnsweredCountByCategory(); - + const renderCategorySection = (catId: string, catLabel: string) => { // Don't render the snoozed category in the normal flow if (catId === 'snoozed') return null; - + const presetForCat = questionsByCategory[catId] || []; const statementsForCat = statementsByCategory[catId] || []; if (presetForCat.length === 0 && statementsForCat.length === 0) return null; // Normalize the category ID for consistent comparison const normalizedCatId = normalizeCategoryIdForGrouping(catId); - + // We're now using a consistent styling regardless of category - + // Use the pre-fetched category counts - const categoryStatus = categoryCounts[normalizedCatId] || { answered: 0, total: 0 }; - const isComplete = categoryStatus.total > 0 && categoryStatus.answered === categoryStatus.total; - - + const categoryStatus = categoryCounts[normalizedCatId] || { + answered: 0, + total: 0, + }; + const isComplete = + categoryStatus.total > 0 && + categoryStatus.answered === categoryStatus.total; + return (
{/* Folder Tab Design */}
-

{formatCategoryName(catLabel)}

- + {/* Folder Content */} -
+
{presetForCat.length > 0 && (
    {presetForCat.map((presetQuestion) => ( @@ -413,24 +435,24 @@ const StatementList: React.FC = ({ // Restore the original entry from our backup setData({ type: 'UPDATE_ENTRY', - payload: originalEntries[statement.id] + payload: originalEntries[statement.id], }); - + // Remove from originalEntries and originalCategories - setOriginalEntries(prev => { - const newEntries = {...prev}; + setOriginalEntries((prev) => { + const newEntries = { ...prev }; delete newEntries[statement.id]; return newEntries; }); - + // Also clear from original categories - setOriginalCategories(prev => { - const newCategories = {...prev}; + setOriginalCategories((prev) => { + const newCategories = { ...prev }; delete newCategories[statement.id]; return newCategories; }); } - + // Exit edit mode setEditingStatementId(null); }} @@ -457,29 +479,44 @@ const StatementList: React.FC = ({
); }; - - + // Special renderer for the snoozed questions section const renderSnoozedQuestionsSection = () => { // Filter questions to get snoozed ones - const snoozedQuestions = questions.filter(q => q.isSnoozed); + const snoozedQuestions = questions.filter((q) => q.isSnoozed); if (snoozedQuestions.length === 0) return null; - + return (
{/* New and improved Snoozed Questions section */} -
+
{/* Header/Tab that's always visible */} -
setIsSnoozedQuestionsSectionExpanded(!isSnoozedQuestionsSectionExpanded)} +
+ setIsSnoozedQuestionsSectionExpanded( + !isSnoozedQuestionsSectionExpanded + ) + } >

Snoozed Questions

- ({snoozedQuestions.length}) + + ({snoozedQuestions.length}) + {isSnoozedQuestionsSectionExpanded ? ( ) : ( @@ -487,16 +524,18 @@ const StatementList: React.FC = ({ )}
- + {/* Content section that appears/disappears */} {isSnoozedQuestionsSectionExpanded && ( -
+
    {snoozedQuestions.map((question) => (
  • {/* Disabled for snoozed questions */}} + onSelect={() => { + /* Disabled for snoozed questions */ + }} onToggleSnooze={handleToggleQuestionSnooze} />
  • @@ -521,55 +560,69 @@ const StatementList: React.FC = ({ // Helper function to find category name from ID const getCategoryName = (categoryId: string): string => { // First check if it's in our defined categories - const category = definedCategories.find(c => - normalizeCategoryIdForGrouping(c.id) === normalizeCategoryIdForGrouping(categoryId) + const category = definedCategories.find( + (c) => + normalizeCategoryIdForGrouping(c.id) === + normalizeCategoryIdForGrouping(categoryId) ); - + if (category) { return category.name; } - + // Check for uncategorized variations - if (['uncategorized', 'uncategorised'].includes(normalizeCategoryIdForGrouping(categoryId))) { + if ( + ['uncategorized', 'uncategorised'].includes( + normalizeCategoryIdForGrouping(categoryId) + ) + ) { return 'Uncategorized'; } - + // If not found, return the ID with formatting return formatCategoryName(categoryId); }; return ( <> -
    +
    {/* Regular categories */} {definedCategories.map((cat) => renderCategorySection(cat.id, cat.name) )} - {extraCategoryIds.map((catId) => renderCategorySection(catId, getCategoryName(catId)))} - + {extraCategoryIds.map((catId) => + renderCategorySection(catId, getCategoryName(catId)) + )} + {/* Snoozed sections */} {renderSnoozedQuestionsSection()} - + {/* Add custom statement section */} {onAddCustomStatement && ( -
    -
    -

    Want to add your own statement?

    -

    - Create a custom statement to add anything that's not covered by the questions above. +

    +
    +

    + Want to add your own statement? +

    +

    + Create a custom statement to add anything that's not covered by + the questions above.

    )} - + = ({ editPart={editModalData.editPart} username={username} onUpdate={(updatedStatement) => { - console.log("====== MODAL UPDATE SEQUENCE START ======"); - console.log("1. EditStatementModal returned updated statement:", updatedStatement); - console.log("2. Current editing state:", { + console.log('====== MODAL UPDATE SEQUENCE START ======'); + console.log( + '1. EditStatementModal returned updated statement:', + updatedStatement + ); + console.log('2. Current editing state:', { isEditing: editingStatementId === updatedStatement.id, - editingStatementId, - updatedStatementId: updatedStatement.id + editingStatementId, + updatedStatementId: updatedStatement.id, }); - + // If we're in editing mode for this statement: if (editingStatementId === updatedStatement.id) { - console.log("3. EDIT MODE PATH: Will update UI without saving to backend"); - console.log("4. Original entries stored:", originalEntries); - + console.log( + '3. EDIT MODE PATH: Will update UI without saving to backend' + ); + console.log('4. Original entries stored:', originalEntries); + // In edit mode, we need to update the data without saving to backend yet // But we must ensure the original values in StatementItem aren't updated - + // Create a completely isolated copy with a unique timestamp const markedEntry = { ...JSON.parse(JSON.stringify(updatedStatement)), @@ -615,44 +673,51 @@ const StatementList: React.FC = ({ _updateTimestamp: Date.now(), _forceUpdate: Math.random().toString(), }; - - console.log("5. Will dispatch UPDATE_ENTRY with:", markedEntry); - + + console.log('5. Will dispatch UPDATE_ENTRY with:', markedEntry); + // Before updating the global state, log the current statements - const existingEntry = entries.find(e => e.id === updatedStatement.id); - console.log("6. Current entries before update:", existingEntry); - + const existingEntry = entries.find( + (e) => e.id === updatedStatement.id + ); + console.log('6. Current entries before update:', existingEntry); + // Log potential category changes - console.log("CATEGORY CHANGE CHECK:", { + console.log('CATEGORY CHANGE CHECK:', { existingCategory: existingEntry?.category, newCategory: markedEntry.category, changed: existingEntry?.category !== markedEntry.category, - equal: existingEntry?.category === markedEntry.category + equal: existingEntry?.category === markedEntry.category, }); - + // Update the UI with the new values setData({ type: 'UPDATE_ENTRY', payload: markedEntry }); - - console.log("7. UPDATE_ENTRY action dispatched"); - + + console.log('7. UPDATE_ENTRY action dispatched'); + // Log what will happen next - console.log("8. This will trigger a re-render of StatementItem with:", { - statementId: updatedStatement.id, - isEditing: true, - newStatementCategory: markedEntry.category, - editingStatementId - }); + console.log( + '8. This will trigger a re-render of StatementItem with:', + { + statementId: updatedStatement.id, + isEditing: true, + newStatementCategory: markedEntry.category, + editingStatementId, + } + ); } else { - console.log("3. DIRECT SAVE PATH: Will update both UI and backend"); - + console.log( + '3. DIRECT SAVE PATH: Will update both UI and backend' + ); + // If we're not in edit mode, save directly when modal is closed setData({ type: 'UPDATE_ENTRY', payload: updatedStatement }); // Also update backend updateEntry(updatedStatement); - - console.log("4. Direct save completed"); + + console.log('4. Direct save completed'); } - console.log("====== MODAL UPDATE SEQUENCE END ======"); + console.log('====== MODAL UPDATE SEQUENCE END ======'); }} onClose={() => setEditModalData(null)} /> diff --git a/src/layouts/components/MainPage.tsx b/src/layouts/components/MainPage.tsx index ae80028..8574c56 100644 --- a/src/layouts/components/MainPage.tsx +++ b/src/layouts/components/MainPage.tsx @@ -62,8 +62,8 @@ const MainPage: React.FC = () => { }; return ( -
    -
    +
    +
    {/* Fixed header layout with 1 or 2 rows */}

    Date: Sun, 23 Mar 2025 14:04:48 +0000 Subject: [PATCH 07/34] style: Removed chevron right on questions --- .../statements/components/QuestionCard.tsx | 59 ++++++++++--------- .../statements/components/StatementList.tsx | 4 +- 2 files changed, 34 insertions(+), 29 deletions(-) diff --git a/src/features/statements/components/QuestionCard.tsx b/src/features/statements/components/QuestionCard.tsx index d9b87fb..923ad60 100644 --- a/src/features/statements/components/QuestionCard.tsx +++ b/src/features/statements/components/QuestionCard.tsx @@ -2,9 +2,13 @@ import React from 'react'; import type { SetQuestion } from '../../../types/entries'; -import { HelpCircle, ChevronRight, BellOff, Bell } from 'lucide-react'; +import { HelpCircle, BellOff, Bell } from 'lucide-react'; import { cn } from '../../../lib/utils'; -import { Tooltip, TooltipTrigger, TooltipContent } from '../../../components/ui/better-tooltip'; +import { + Tooltip, + TooltipTrigger, + TooltipContent, +} from '../../../components/ui/better-tooltip'; export interface QuestionCardProps { presetQuestion: SetQuestion; @@ -24,10 +28,12 @@ const QuestionCard: React.FC = ({ }; return ( -
    +
    {/* Question Card */} -
    {} : () => onSelect(presetQuestion)} +
    {} : () => onSelect(presetQuestion) + } className={cn( presetQuestion.isSnoozed ? 'cursor-default' : 'cursor-pointer', presetQuestion.isSnoozed && 'opacity-60' @@ -35,7 +41,7 @@ const QuestionCard: React.FC = ({ >
    = ({ {!presetQuestion.isSnoozed && ( - + @@ -54,46 +60,45 @@ const QuestionCard: React.FC = ({ )} - + {/* Main question text (truncated if too long) */} {presetQuestion.mainQuestion} - + {/* Snooze/Unsnooze button */} -
    +
    - - {presetQuestion.isSnoozed ? 'Unsnooze question' : 'Snooze question'} + {presetQuestion.isSnoozed + ? 'Unsnooze question' + : 'Snooze question'}
    - - {/* Chevron icon to indicate clickability - only for non-snoozed questions */} - {!presetQuestion.isSnoozed && ( - - - - )}
    diff --git a/src/features/statements/components/StatementList.tsx b/src/features/statements/components/StatementList.tsx index 3d69b8e..ab7eab4 100644 --- a/src/features/statements/components/StatementList.tsx +++ b/src/features/statements/components/StatementList.tsx @@ -381,7 +381,7 @@ const StatementList: React.FC = ({ categoryStatus.answered === categoryStatus.total; return ( -
    +
    {/* Folder Tab Design */}
    = ({ {/* Folder Content */}
    Date: Sun, 23 Mar 2025 17:23:03 +0000 Subject: [PATCH 08/34] fix: FilterBar will be conditionally rendered on small displays --- src/components/ui/buttonVariants.ts | 2 +- .../statements/components/QuestionCard.tsx | 10 ++-- .../statements/components/StatementList.tsx | 2 +- .../wizard/components/SentimentVerbPicker.tsx | 43 +++++++++++++---- .../wizard/components/StatementPreview.tsx | 8 ++-- .../wizard/components/StatementWizard.tsx | 36 +++++++++------ .../wizard/components/StepContainer.tsx | 4 +- .../wizard/components/SubjectTiles.tsx | 2 +- src/features/wizard/components/VerbGrid.tsx | 4 +- .../wizard/components/VerbSelector.tsx | 2 +- .../wizard/components/steps/VerbStep.tsx | 4 +- src/layouts/components/Footer.tsx | 46 +++++++++++++++++++ src/layouts/components/MainPage.tsx | 37 ++------------- 13 files changed, 124 insertions(+), 76 deletions(-) create mode 100644 src/layouts/components/Footer.tsx diff --git a/src/components/ui/buttonVariants.ts b/src/components/ui/buttonVariants.ts index db6c621..c9f7bbf 100644 --- a/src/components/ui/buttonVariants.ts +++ b/src/components/ui/buttonVariants.ts @@ -12,7 +12,7 @@ export const buttonVariants = cva( outline: 'border border-pink-200 bg-white text-gray-700 hover:border-pink-300 transition-colors', outlineVerbs: - 'border border-pink-200 bg-white text-gray-700 hover:bg-brand-pink hover:border-pink-300 transition-colors', + 'border-2 bg-white text-gray-700 hover:bg-brand-pink hover:border-pink-300 transition-colors', secondary: 'bg-pink-100 text-brand-pink border border-pink-200 transition-colors', ghost: 'hover:bg-pink-50 hover:text-brand-pink transition-colors', diff --git a/src/features/statements/components/QuestionCard.tsx b/src/features/statements/components/QuestionCard.tsx index 923ad60..c53a0dd 100644 --- a/src/features/statements/components/QuestionCard.tsx +++ b/src/features/statements/components/QuestionCard.tsx @@ -41,9 +41,9 @@ const QuestionCard: React.FC = ({ >
    @@ -51,7 +51,7 @@ const QuestionCard: React.FC = ({ {!presetQuestion.isSnoozed && ( - + @@ -62,7 +62,7 @@ const QuestionCard: React.FC = ({ )} {/* Main question text (truncated if too long) */} - + {presetQuestion.mainQuestion} @@ -73,7 +73,7 @@ const QuestionCard: React.FC = ({
    - + + {/* Divider to separate preview from wizard content */} +
    + + Statement Preview + +
    + + ); diff --git a/src/features/wizard/components/StepContainer.tsx b/src/features/wizard/components/StepContainer.tsx index d9678bc..b57918b 100644 --- a/src/features/wizard/components/StepContainer.tsx +++ b/src/features/wizard/components/StepContainer.tsx @@ -28,12 +28,12 @@ const StepContainer: React.FC = ({ variant='outline' size='compact' onClick={onBack} - className='mr-2 p-2' + className='mr-2 p-0 md:p-2' > )} -
    +
    {currentStep}/{totalSteps} diff --git a/src/features/wizard/components/SubjectTiles.tsx b/src/features/wizard/components/SubjectTiles.tsx index 703e243..eab07f6 100644 --- a/src/features/wizard/components/SubjectTiles.tsx +++ b/src/features/wizard/components/SubjectTiles.tsx @@ -57,7 +57,7 @@ export const SubjectTiles: React.FC = ({ key={tile.value} variant={'outline'} selected={selectedValue === tile.value} - className={`h-auto py-4 px-6 text-left flex flex-col items-start space-y-1 + className={`h-auto py-2 px-2 text-left flex flex-col items-start space-y-1 `} onClick={() => onSelect(tile.value)} diff --git a/src/features/wizard/components/VerbGrid.tsx b/src/features/wizard/components/VerbGrid.tsx index 16a58bf..fa9b147 100644 --- a/src/features/wizard/components/VerbGrid.tsx +++ b/src/features/wizard/components/VerbGrid.tsx @@ -21,7 +21,7 @@ const VerbGrid: React.FC = ({ }) => { const sortedVerbs = [...verbs].sort((a, b) => a.name.localeCompare(b.name)); return ( -
    +
    {sortedVerbs.map((verb) => { const tileColor = getVerbColor(verb, rootCategory); @@ -32,7 +32,7 @@ const VerbGrid: React.FC = ({ onClick={() => onVerbSelect(verb)} variant={'outlineVerbs'} selected={isSelected} - className='flex items-center justify-center p-4 rounded-lg shadow-md' + className='flex items-center justify-center rounded-lg shadow-md' style={ { '--tile-color': tileColor, diff --git a/src/features/wizard/components/VerbSelector.tsx b/src/features/wizard/components/VerbSelector.tsx index beb48d3..ecd1116 100644 --- a/src/features/wizard/components/VerbSelector.tsx +++ b/src/features/wizard/components/VerbSelector.tsx @@ -154,7 +154,7 @@ const VerbSelector: React.FC = ({ onVerbSelect }) => { }; return ( -
    +
    {categoryPath.length > 0 && ( diff --git a/src/features/wizard/components/steps/VerbStep.tsx b/src/features/wizard/components/steps/VerbStep.tsx index cb385c8..be642a2 100644 --- a/src/features/wizard/components/steps/VerbStep.tsx +++ b/src/features/wizard/components/steps/VerbStep.tsx @@ -21,12 +21,12 @@ export const VerbStep: React.FC = ({ const subQuestion = `What's happening with ${subject}? How do they feel or what do they experience?`; return ( - -
    +
    { diff --git a/src/layouts/components/Footer.tsx b/src/layouts/components/Footer.tsx new file mode 100644 index 0000000..b83dcab --- /dev/null +++ b/src/layouts/components/Footer.tsx @@ -0,0 +1,46 @@ +'use client'; + +import React, { useState } from 'react'; +import PrivacyModal from '../../components/modals/PrivacyModal'; +import TermsModal from '../../components/modals/TermsModal'; + +const Footer: React.FC = () => { + const [isPrivacyModalOpen, setIsPrivacyModalOpen] = useState(false); + const [isTermsModalOpen, setIsTermsModalOpen] = useState(false); + + return ( + <> +
    +
    +
    + + + © {new Date().getFullYear()} Beacons +
    +
    +
    + + {/* Conditionally render the privacy modal */} + {isPrivacyModalOpen && ( + setIsPrivacyModalOpen(false)} /> + )} + + {/* Conditionally render the terms modal */} + {isTermsModalOpen && ( + setIsTermsModalOpen(false)} /> + )} + + ); +}; + +export default Footer; \ No newline at end of file diff --git a/src/layouts/components/MainPage.tsx b/src/layouts/components/MainPage.tsx index 8574c56..9114ade 100644 --- a/src/layouts/components/MainPage.tsx +++ b/src/layouts/components/MainPage.tsx @@ -12,9 +12,8 @@ import { Button } from '../../components/ui/button'; import { Mail } from 'lucide-react'; import StatementWizard from '../../features/wizard/components/StatementWizard'; import ShareEmailModal from '../../components/modals/ShareEmailModal'; -import PrivacyModal from '../../components/modals/PrivacyModal'; -import TermsModal from '../../components/modals/TermsModal'; import TestStatementButton from '../../components/debug/TestButton'; +import Footer from './Footer'; // import { useTour } from '../../components/ui/tour/useTour'; const MainPage: React.FC = () => { @@ -22,8 +21,6 @@ const MainPage: React.FC = () => { const { username, managerName, managerEmail, entries } = data; const [isWizardOpen, setIsWizardOpen] = useState(false); const [isShareModalOpen, setIsShareModalOpen] = useState(false); - const [isPrivacyModalOpen, setIsPrivacyModalOpen] = useState(false); - const [isTermsModalOpen, setIsTermsModalOpen] = useState(false); // Get tour functionality // const { startTour, hasSeenTour } = useTour(); @@ -84,26 +81,8 @@ const MainPage: React.FC = () => { />
    - {/* Footer with privacy links */} -
    -
    -
    - - - © {new Date().getFullYear()} Beacons -
    -
    -
    + {/* Footer component */} +
    {/* Floating Email Button (now singular) */}
    @@ -153,16 +132,6 @@ const MainPage: React.FC = () => { {isShareModalOpen && ( setIsShareModalOpen(false)} /> )} - - {/* Conditionally render the privacy modal */} - {isPrivacyModalOpen && ( - setIsPrivacyModalOpen(false)} /> - )} - - {/* Conditionally render the terms modal */} - {isTermsModalOpen && ( - setIsTermsModalOpen(false)} /> - )}

    ); }; From 9c49e2c10d7307eb1ea3d9667ac943510c6b0b99 Mon Sep 17 00:00:00 2001 From: Alexander Date: Sun, 23 Mar 2025 17:43:58 +0000 Subject: [PATCH 09/34] style: conditional styled privacy on wizard --- src/components/ui/buttonVariants.ts | 8 +- .../wizard/components/StatementWizard.tsx | 4 +- .../wizard/components/steps/ObjectStep.tsx | 4 +- .../wizard/components/steps/PrivacyStep.tsx | 97 ++++++++++--------- src/index.css | 4 +- 5 files changed, 62 insertions(+), 55 deletions(-) diff --git a/src/components/ui/buttonVariants.ts b/src/components/ui/buttonVariants.ts index c9f7bbf..f8e36a3 100644 --- a/src/components/ui/buttonVariants.ts +++ b/src/components/ui/buttonVariants.ts @@ -1,7 +1,7 @@ import { cva } from 'class-variance-authority'; export const buttonVariants = cva( - 'inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50', + 'inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50', { variants: { variant: { @@ -10,7 +10,7 @@ export const buttonVariants = cva( destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90', outline: - 'border border-pink-200 bg-white text-gray-700 hover:border-pink-300 transition-colors', + 'border border-pink-200 bg-white text-gray-700 hover:border-pink-300 transition-colors focus:border-pink-200 focus-visible:border-pink-200', outlineVerbs: 'border-2 bg-white text-gray-700 hover:bg-brand-pink hover:border-pink-300 transition-colors', secondary: @@ -32,8 +32,8 @@ export const buttonVariants = cva( compact: 'h-8 px-3 py-1', }, selected: { - true: 'bg-[var(--tile-color)]', - false: '', + true: 'border-4 border-[var(--tile-color)] focus:ring-0 focus:border-[var(--tile-color)] focus-visible:ring-0', + false: 'focus:ring-0 focus-visible:ring-0', }, }, defaultVariants: { diff --git a/src/features/wizard/components/StatementWizard.tsx b/src/features/wizard/components/StatementWizard.tsx index 8094c27..9a33fa4 100644 --- a/src/features/wizard/components/StatementWizard.tsx +++ b/src/features/wizard/components/StatementWizard.tsx @@ -343,8 +343,8 @@ const StatementWizard: React.FC = ({
    {/* Divider to separate preview from wizard content */} -
    - +
    + Statement Preview
    diff --git a/src/features/wizard/components/steps/ObjectStep.tsx b/src/features/wizard/components/steps/ObjectStep.tsx index 5c30eba..f883de9 100644 --- a/src/features/wizard/components/steps/ObjectStep.tsx +++ b/src/features/wizard/components/steps/ObjectStep.tsx @@ -26,7 +26,7 @@ export const ObjectStep: React.FC = ({ )}? What's the context?`; return ( - = ({ placeholder='Type your answer...' value={selection} onChange={(e) => onUpdate(e.target.value)} - className='text-lg p-4 rounded' + className='text-lg p-4 rounded text-black' />
    diff --git a/src/features/wizard/components/steps/PrivacyStep.tsx b/src/features/wizard/components/steps/PrivacyStep.tsx index da5d5c6..9e8fad9 100644 --- a/src/features/wizard/components/steps/PrivacyStep.tsx +++ b/src/features/wizard/components/steps/PrivacyStep.tsx @@ -2,6 +2,7 @@ import React from 'react'; import StepContainer from '../StepContainer'; import { Button } from '@/components/ui/button'; import { MailX, MailPlus } from 'lucide-react'; +import { useEntries } from '@/features/statements/hooks/useEntries'; interface PrivacyStepProps { isPublic: boolean; @@ -18,62 +19,68 @@ export const PrivacyStep: React.FC = ({ currentStep = 5, totalSteps = 5, }) => { + const { data } = useEntries(); + const { managerName } = data; const subQuestion = 'Who can see this statement?'; return ( - -
    -
    - - + -
    +
    +
    ); diff --git a/src/index.css b/src/index.css index 848bb5c..ed626a5 100644 --- a/src/index.css +++ b/src/index.css @@ -56,8 +56,8 @@ --verb-selector-hover: #a0d8f1; --object-input: #bfdbfe; --object-input-hover: #aecbf0; - --privacy-selector: #f0a3d3; - --privacy-selector-hover: #e68fc0; + --privacy-selector: #aa2f4a; + --privacy-selector-hover: #8b243b; --category-selector: #ffc394; --category-selector-hover: #ffb57a; --complement: 'gray-400'; From 5f1f16633d5951ea4414cfef013e70a470d0d7b4 Mon Sep 17 00:00:00 2001 From: Alexander Date: Sun, 23 Mar 2025 18:05:45 +0000 Subject: [PATCH 10/34] fix: restyle statement menu --- src/components/modals/ShareEmailModal.tsx | 8 ++-- src/components/ui/simple-dropdown.tsx | 2 +- .../statements/components/StatementItem.tsx | 48 ++++++++++--------- .../statements/components/StatementList.tsx | 2 +- src/types/entries.ts | 2 +- 5 files changed, 33 insertions(+), 29 deletions(-) diff --git a/src/components/modals/ShareEmailModal.tsx b/src/components/modals/ShareEmailModal.tsx index 781b413..b6467f1 100644 --- a/src/components/modals/ShareEmailModal.tsx +++ b/src/components/modals/ShareEmailModal.tsx @@ -18,9 +18,9 @@ const ShareEmailModal: React.FC<{ onClose: () => void }> = ({ onClose }) => { const [sendSuccess, setSendSuccess] = useState(false); const [isPrivacyModalOpen, setIsPrivacyModalOpen] = useState(false); - // Only include public statements that are not resolved + // Only include public statements that are not archived const publicStatements = data.entries.filter( - (entry) => entry.isPublic && !entry.isResolved + (entry) => entry.isPublic && !entry.isArchived ); const generateEmailHtml = () => { @@ -145,7 +145,7 @@ const ShareEmailModal: React.FC<{ onClose: () => void }> = ({ onClose }) => {
    - Below are your public, unresolved statements + Below are your public, unarchived statements
    @@ -225,7 +225,7 @@ const ShareEmailModal: React.FC<{ onClose: () => void }> = ({ onClose }) => { )) ) : (
    - No public unresolved statements available. + No public unarchived statements available.
    )}
    diff --git a/src/components/ui/simple-dropdown.tsx b/src/components/ui/simple-dropdown.tsx index 98a401d..ea23c1d 100644 --- a/src/components/ui/simple-dropdown.tsx +++ b/src/components/ui/simple-dropdown.tsx @@ -127,7 +127,7 @@ const SimpleDropdownMenuContent = React.forwardRef = ({
    {/* Archived badge - positioned in top right corner */} - {statement.isResolved && ( + {statement.isArchived && ( Archived @@ -685,7 +684,7 @@ const StatementItem: React.FC = ({ {statement.isPublic ? ( @@ -703,7 +702,7 @@ const StatementItem: React.FC = ({ {/* Statement text with archived styling if needed */}
    - + {`${statement.atoms.subject} ${getVerbName( statement.atoms.verb )} ${statement.atoms.object}`} @@ -711,7 +710,7 @@ const StatementItem: React.FC = ({
    - {statement.isResolved && ( + {/* {statement.isArchived && ( @@ -719,10 +718,10 @@ const StatementItem: React.FC = ({ - This statement is resolved. + This statement is archived. - )} + )} */}
    setIsActionsExpanded((prev) => !prev)} className='cursor-pointer' @@ -734,8 +733,11 @@ const StatementItem: React.FC = ({
    - @@ -751,7 +753,7 @@ const StatementItem: React.FC = ({ Delete onToggleResolved(statement.id)}> - {statement.isResolved ? ( + {statement.isArchived ? ( <> Unarchive @@ -797,8 +799,10 @@ const StatementItem: React.FC = ({ // Create a copy of the entire statement const statementToUpdate = { ...statement }; const updatedActions = [...(statementToUpdate.actions || [])]; - const actionIndex = updatedActions.findIndex(a => a.id === actionId); - + const actionIndex = updatedActions.findIndex( + (a) => a.id === actionId + ); + if (actionIndex !== -1) { // Create an updated action with gratitude information const action = updatedActions[actionIndex]; @@ -807,24 +811,24 @@ const StatementItem: React.FC = ({ gratitude: { sent: true, message: message, - sentDate: new Date().toISOString() - } + sentDate: new Date().toISOString(), + }, }; - + // Replace the action in the array updatedActions[actionIndex] = updatedAction; - + // Update the statement with the updated actions statementToUpdate.actions = updatedActions; - + // Call onEditAction for UI updates (but it won't save the gratitude fields) if (onEditAction) { onEditAction(statement.id, actionId, { text: action.action, - dueDate: action.byDate + dueDate: action.byDate, }); } - + // Call onLocalSave to save the entire updated statement with gratitude info if (onLocalSave) { onLocalSave(statementToUpdate); diff --git a/src/features/statements/components/StatementList.tsx b/src/features/statements/components/StatementList.tsx index ee9a731..980ef54 100644 --- a/src/features/statements/components/StatementList.tsx +++ b/src/features/statements/components/StatementList.tsx @@ -117,7 +117,7 @@ const StatementList: React.FC = ({ const handleToggleResolved = (statementId: string) => { const stmt = entries.find((s) => s.id === statementId); if (!stmt) return; - const updated = { ...stmt, isResolved: !stmt.isResolved }; + const updated = { ...stmt, isArchived: !stmt.isArchived }; setData({ type: 'UPDATE_ENTRY', payload: updated }); updateEntry(updated); }; diff --git a/src/types/entries.ts b/src/types/entries.ts index 49d9a69..bce29ba 100644 --- a/src/types/entries.ts +++ b/src/types/entries.ts @@ -26,7 +26,7 @@ export interface Entry { actions?: Action[]; category: string; presetId?: string; - isResolved?: boolean; + isArchived?: boolean; _needsScroll?: boolean; // Flag to indicate if this entry needs to be scrolled into view _updateTimestamp?: number; // Optional timestamp for tracking updates } From 294a77b94a018b3ffa3c8e06b4977d50af2b8dd2 Mon Sep 17 00:00:00 2001 From: Alexander Date: Sun, 23 Mar 2025 18:18:59 +0000 Subject: [PATCH 11/34] style: set lower cut point for changing actions button in 2 rows --- .../statements/components/ActionsCounter.tsx | 2 +- .../statements/components/StatementItem.tsx | 116 ++++++++++++++++-- 2 files changed, 104 insertions(+), 14 deletions(-) diff --git a/src/features/statements/components/ActionsCounter.tsx b/src/features/statements/components/ActionsCounter.tsx index e8d0f0c..cb48d86 100644 --- a/src/features/statements/components/ActionsCounter.tsx +++ b/src/features/statements/components/ActionsCounter.tsx @@ -24,7 +24,7 @@ const ActionsCounter: React.FC = ({ // Determine the display text. let displayText = ''; if (count === 0) { - displayText = 'no actions'; + displayText = 'No actions'; } else if (count === 1) { displayText = '1 action'; } else { diff --git a/src/features/statements/components/StatementItem.tsx b/src/features/statements/components/StatementItem.tsx index ca9d4f5..f239952 100644 --- a/src/features/statements/components/StatementItem.tsx +++ b/src/features/statements/components/StatementItem.tsx @@ -676,7 +676,8 @@ const StatementItem: React.FC = ({ )} -
    + {/* Desktop layout (larger than 580px) */} +
    {/* Privacy status icon */} @@ -710,18 +711,6 @@ const StatementItem: React.FC = ({
    - {/* {statement.isArchived && ( - - - - - - - - This statement is archived. - - - )} */}
    setIsActionsExpanded((prev) => !prev)} className='cursor-pointer' @@ -777,6 +766,107 @@ const StatementItem: React.FC = ({
    + {/* Mobile layout (smaller than 580px) - Two row layout */} +
    + {/* First row: Privacy icon, Statement text, Menu icon */} +
    +
    + {/* Privacy status icon */} + + + + {statement.isPublic ? ( + + ) : ( + + )} + + + + {statement.isPublic + ? 'You are sharing this statement' + : 'This statement is private'} + + + + {/* Statement text with archived styling if needed */} +
    + + {`${statement.atoms.subject} ${getVerbName( + statement.atoms.verb + )} ${statement.atoms.object}`} + +
    +
    + + {/* Menu dropdown */} + + + + + + onEditClick(statement.id)}> + + Edit + + onDelete(statement.id)} + className='text-red-600' + > + + Delete + + onToggleResolved(statement.id)}> + {statement.isArchived ? ( + <> + + Unarchive + + ) : ( + <> + + Archive + + )} + + + {onReset && ( + onReset(statement.id)}> + + Reset + + )} + + +
    + + {/* Second row: Action counter */} +
    +
    setIsActionsExpanded((prev) => !prev)} + className='cursor-pointer' + > + +
    +
    +
    + {isActionsExpanded && (
    Date: Sun, 23 Mar 2025 18:53:38 +0000 Subject: [PATCH 12/34] Style: actions shown as tab --- .../statements/components/ActionLine.tsx | 34 ++++++++++------- .../statements/components/ActionsCounter.tsx | 32 ++++++++-------- .../statements/components/StatementItem.tsx | 38 ++++++++++--------- 3 files changed, 56 insertions(+), 48 deletions(-) diff --git a/src/features/statements/components/ActionLine.tsx b/src/features/statements/components/ActionLine.tsx index f58e857..0c5728a 100644 --- a/src/features/statements/components/ActionLine.tsx +++ b/src/features/statements/components/ActionLine.tsx @@ -21,10 +21,10 @@ import { CheckCircle2, XCircle } from 'lucide-react'; import GratitudeModal from '../../../components/modals/GratitudeModal'; import { markGratitudeSent } from '../../../features/email/api/gratitudeApi'; import { useEntries } from '../hooks/useEntries'; -import { - Tooltip, - TooltipTrigger, - TooltipContent +import { + Tooltip, + TooltipTrigger, + TooltipContent, } from '../../../components/ui/better-tooltip'; export interface ActionLineProps { @@ -61,7 +61,7 @@ const ActionLine: React.FC = ({ isOpen: boolean; action: Action | null; }>({ isOpen: false, action: null }); - + // Get entries data to check for manager email const { data } = useEntries(); const hasManagerEmail = data.managerEmail && data.managerEmail.trim() !== ''; @@ -114,7 +114,7 @@ const ActionLine: React.FC = ({ }; return ( -
    +
    {actions.map((action) => { const isEditing = editingActionId === action.id; if (!isEditing) { @@ -152,19 +152,21 @@ const ActionLine: React.FC = ({ className='text-pink-500' /> {/* Small dot indicator */} - +
    -
    -

    Gratitude Sent

    +
    +

    Gratitude Sent

    {action.gratitude?.sentDate && ( -

    - {new Date(action.gratitude.sentDate).toLocaleDateString()} +

    + {new Date( + action.gratitude.sentDate + ).toLocaleDateString()}

    )} {action.gratitude?.message && ( -

    +

    "{action.gratitude.message}"

    )} @@ -223,14 +225,18 @@ const ActionLine: React.FC = ({ {/* Determine if manager email is set */} -
    +
    { if (hasManagerEmail) { setGratitudeModal({ isOpen: true, action }); } }} - className={`${hasManagerEmail ? 'text-pink-600' : 'text-pink-300 cursor-not-allowed'}`} + className={`${ + hasManagerEmail + ? 'text-pink-600' + : 'text-pink-300 cursor-not-allowed' + }`} disabled={!hasManagerEmail} > diff --git a/src/features/statements/components/ActionsCounter.tsx b/src/features/statements/components/ActionsCounter.tsx index cb48d86..b91e2d3 100644 --- a/src/features/statements/components/ActionsCounter.tsx +++ b/src/features/statements/components/ActionsCounter.tsx @@ -1,7 +1,7 @@ 'use client'; -import React, { useRef, useState, useEffect } from 'react'; -import { ChevronDown, ChevronUp, List } from 'lucide-react'; +import React, { useRef } from 'react'; +import { ChevronDown, ChevronUp } from 'lucide-react'; interface ActionsCounterProps { count: number; @@ -13,13 +13,20 @@ const ActionsCounter: React.FC = ({ expanded = false, }) => { const containerRef = useRef(null); - const [isOverflowing, setIsOverflowing] = useState(false); - // We force a max width so the text might actually overflow. + // Tab-like styling similar to category tabs const baseClasses = - 'inline-flex items-center rounded-full px-3 py-1 text-sm transition-colors cursor-pointer whitespace-nowrap max-w-[150px] overflow-hidden'; + 'inline-flex items-center px-3 py-1 text-sm transition-colors cursor-pointer whitespace-nowrap'; + + // Determine border radius and border styling based on expanded state + const borderStyles = expanded + ? 'rounded-t-lg border-t border-l border-r' + : 'rounded-lg border'; + const backgroundClasses = - count > 0 ? 'bg-brand-pink text-white' : 'bg-gray-100 text-gray-600'; + count > 0 + ? `bg-brand-pink text-white ${borderStyles} border-brand-pink` + : `bg-slate-100 text-gray-600 ${borderStyles} border-slate-300`; // Determine the display text. let displayText = ''; @@ -31,19 +38,10 @@ const ActionsCounter: React.FC = ({ displayText = `${count} actions`; } - useEffect(() => { - if (!containerRef.current) return; - const el = containerRef.current; - // Compare the actual content width vs. the visible width. - setIsOverflowing(el.scrollWidth > el.clientWidth); - }, [count, displayText]); - - // If overflowing, show an icon; otherwise show the text. - const content = isOverflowing ? : displayText; - + // Always show text for tab-like design return ( - {content} + {displayText} {expanded ? : } diff --git a/src/features/statements/components/StatementItem.tsx b/src/features/statements/components/StatementItem.tsx index f239952..45c95d8 100644 --- a/src/features/statements/components/StatementItem.tsx +++ b/src/features/statements/components/StatementItem.tsx @@ -662,7 +662,7 @@ const StatementItem: React.FC = ({
    = ({
    -
    setIsActionsExpanded((prev) => !prev)} - className='cursor-pointer' - > - -
    + {/* Menu button */}
    {/* Mobile layout (smaller than 580px) - Two row layout */}
    - {/* First row: Privacy icon, Statement text, Menu icon */} + {/* First row: Privacy icon, Statement text, Menu button */}
    {/* Privacy status icon */} @@ -807,12 +810,12 @@ const StatementItem: React.FC = ({
    - {/* Menu dropdown */} + {/* Menu button */} @@ -853,11 +856,12 @@ const StatementItem: React.FC = ({
    - {/* Second row: Action counter */} -
    + {/* Second row: Action counter (right aligned) */} +
    + {/* Action counter - rightmost element */}
    setIsActionsExpanded((prev) => !prev)} - className='cursor-pointer' + className='cursor-pointer relative z-10 flex-shrink-0' > = ({
    {isActionsExpanded && ( -
    +
    Date: Sun, 23 Mar 2025 19:00:24 +0000 Subject: [PATCH 13/34] feat: added xs screen size on tailwind --- src/features/statements/components/StatementItem.tsx | 8 ++++---- tailwind.config.js | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/features/statements/components/StatementItem.tsx b/src/features/statements/components/StatementItem.tsx index 45c95d8..af062c2 100644 --- a/src/features/statements/components/StatementItem.tsx +++ b/src/features/statements/components/StatementItem.tsx @@ -676,8 +676,8 @@ const StatementItem: React.FC = ({ )} - {/* Desktop layout (larger than 580px) */} -
    + {/* Desktop layout (xs breakpoint and above) */} +
    {/* Privacy status icon */} @@ -769,8 +769,8 @@ const StatementItem: React.FC = ({
    - {/* Mobile layout (smaller than 580px) - Two row layout */} -
    + {/* Mobile layout (smaller than xs breakpoint) - Two row layout */} +
    {/* First row: Privacy icon, Statement text, Menu button */}
    diff --git a/tailwind.config.js b/tailwind.config.js index 6c7a6f4..c5f254a 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -15,8 +15,8 @@ export default { }, }, screens: { - xs: '480px', - sm: '480px', + xs: '580px', + sm: '640px', md: '768px', lg: '1024px', xl: '1280px', From e3c980f551469ed5c6a41a2171cef737cf09210d Mon Sep 17 00:00:00 2001 From: Alexander Date: Sun, 23 Mar 2025 20:11:46 +0000 Subject: [PATCH 14/34] style: use brand colors for actions group border --- src/components/ui/input.tsx | 2 +- .../statements/components/ActionForm.tsx | 58 +++++++- .../statements/components/ActionLine.tsx | 124 +++++++++++------- .../statements/components/QuestionCard.tsx | 2 +- .../statements/components/StatementItem.tsx | 16 +-- .../statements/components/StatementList.tsx | 6 +- 6 files changed, 147 insertions(+), 61 deletions(-) diff --git a/src/components/ui/input.tsx b/src/components/ui/input.tsx index b149f19..38d70f0 100644 --- a/src/components/ui/input.tsx +++ b/src/components/ui/input.tsx @@ -10,7 +10,7 @@ const Input = React.forwardRef< = ({ return (
    -
    + {/* Desktop layout (xs and up) - all in one row */} +
    = ({
    + + {/* Mobile layout (below xs) - three rows */} +
    + {/* Row 1: Action text */} +
    + setText(e.target.value)} + className='w-full' + /> +
    + + {/* Row 2: Date field with label on same row */} +
    + + setDueDate(e.target.value)} + className='flex-1' + /> +
    + + {/* Row 3: Buttons */} +
    + + +
    +
    + + {/* Due date helper text - only show on desktop view */} {!dueDate.trim() && ( -
    +
    No due date selected (optional)
    )} diff --git a/src/features/statements/components/ActionLine.tsx b/src/features/statements/components/ActionLine.tsx index 0c5728a..e24f8a3 100644 --- a/src/features/statements/components/ActionLine.tsx +++ b/src/features/statements/components/ActionLine.tsx @@ -128,60 +128,92 @@ const ActionLine: React.FC = ({ return (
    - {/* Text is placed on the left, taking up all remaining space with "flex-1". */} - {action.action} + {/* Resolved badge - positioned in top right corner similar to archived badge */} + {action.completed && ( + + + Resolved + + )} + {/* Desktop layout (larger than xs breakpoint) */} +
    + {action.action} - {/* Right side holds icons and dropdown menu. */} -
    - {/* Show resolved icon if action.completed is true. */} - {action.completed && ( - - )} + {/* Right side holds icons and dropdown menu. */} +
    - {/* Show gratitude sent icon with tooltip */} - {action.gratitude?.sent && ( - - -
    - - {/* Small dot indicator */} - -
    -
    - -
    -

    Gratitude Sent

    - {action.gratitude?.sentDate && ( -

    - {new Date( - action.gratitude.sentDate - ).toLocaleDateString()} -

    - )} - {action.gratitude?.message && ( -

    - "{action.gratitude.message}" -

    - )} -
    -
    -
    - )} + {/* Show gratitude sent icon with tooltip */} + {action.gratitude?.sent && ( + + +
    + + {/* Small dot indicator */} + +
    +
    + +
    +

    Gratitude Sent

    + {action.gratitude?.sentDate && ( +

    + {new Date( + action.gratitude.sentDate + ).toLocaleDateString()} +

    + )} + {action.gratitude?.message && ( +

    + "{action.gratitude.message}" +

    + )} +
    +
    +
    + )} - {/* Show due date if present. */} + {/* Show due date if present. */} + {dueDateText && ( + + Due: {dueDateText} + + )} +
    +
    + + {/* Mobile layout (smaller than xs breakpoint) - two rows if due date exists */} +
    + {/* First row: action text */} +
    + {action.action} + + {/* Status icons */} +
    + {action.gratitude?.sent && ( + + )} +
    +
    + + {/* Second row: due date if exists */} {dueDateText && ( - - Due: {dueDateText} - +
    + + Due: {dueDateText} + +
    )} - +
    + + {/* Dropdown menu for both layouts - aligned to top */} +

({snoozedQuestions.length}) @@ -527,7 +510,7 @@ const StatementList: React.FC = ({ {/* Content section that appears/disappears */} {isSnoozedQuestionsSectionExpanded && ( -
+
    {snoozedQuestions.map((question) => (
  • @@ -587,7 +570,7 @@ const StatementList: React.FC = ({ <>
    {/* Regular categories */} {definedCategories.map((cat) => @@ -597,31 +580,29 @@ const StatementList: React.FC = ({ renderCategorySection(catId, getCategoryName(catId)) )} - {/* Snoozed sections */} - {renderSnoozedQuestionsSection()} - {/* Add custom statement section */} - {onAddCustomStatement && ( -
    -
    -

    - Want to add your own statement? -

    -

    - Create a custom statement to add anything that's not covered by - the questions above. -

    - -
    +
    +
    +

    + Want to add your own statement? +

    + +
    - )} +
    + + {/* Snoozed sections */} + {renderSnoozedQuestionsSection()} = ({ description='Are you sure you want to delete this statement? This action cannot be undone.' />
    - {isWizardOpen && selectedPresetQuestion && ( + {isWizardOpen && ( Date: Sun, 23 Mar 2025 21:04:55 +0000 Subject: [PATCH 16/34] fix: fixed syle for gratitud --- src/components/modals/GratitudeModal.tsx | 7 ++- .../statements/components/ActionLine.tsx | 52 +++++++------------ 2 files changed, 22 insertions(+), 37 deletions(-) diff --git a/src/components/modals/GratitudeModal.tsx b/src/components/modals/GratitudeModal.tsx index 79b2cd3..0509d26 100644 --- a/src/components/modals/GratitudeModal.tsx +++ b/src/components/modals/GratitudeModal.tsx @@ -61,10 +61,10 @@ const GratitudeModal: React.FC = ({ }; // Check if we're in mock mode - const isMockMode = - typeof import.meta.env.VITE_MOCK_EMAIL_SENDING === 'undefined' || + const isMockMode = + typeof import.meta.env.VITE_MOCK_EMAIL_SENDING === 'undefined' || import.meta.env.VITE_MOCK_EMAIL_SENDING === 'true'; - + try { // Try to send gratitude via API await sendGratitude(gratitudeRequest); @@ -141,7 +141,6 @@ const GratitudeModal: React.FC = ({

    - Action

    {action.action}

    diff --git a/src/features/statements/components/ActionLine.tsx b/src/features/statements/components/ActionLine.tsx index e24f8a3..9ab56db 100644 --- a/src/features/statements/components/ActionLine.tsx +++ b/src/features/statements/components/ActionLine.tsx @@ -145,7 +145,6 @@ const ActionLine: React.FC = ({ {/* Right side holds icons and dropdown menu. */}
    - {/* Show gratitude sent icon with tooltip */} {action.gratitude?.sent && ( @@ -187,13 +186,13 @@ const ActionLine: React.FC = ({ )}
    - + {/* Mobile layout (smaller than xs breakpoint) - two rows if due date exists */}
    {/* First row: action text */}
    {action.action} - + {/* Status icons */}
    {action.gratitude?.sent && ( @@ -201,7 +200,7 @@ const ActionLine: React.FC = ({ )}
    - + {/* Second row: due date if exists */} {dueDateText && (
    @@ -211,7 +210,7 @@ const ActionLine: React.FC = ({
    )}
    - + {/* Dropdown menu for both layouts - aligned to top */}
    @@ -254,34 +253,21 @@ const ActionLine: React.FC = ({ {!action.gratitude?.sent && ( <> - {/* Determine if manager email is set */} - - -
    - { - if (hasManagerEmail) { - setGratitudeModal({ isOpen: true, action }); - } - }} - className={`${ - hasManagerEmail - ? 'text-pink-600' - : 'text-pink-300 cursor-not-allowed' - }`} - disabled={!hasManagerEmail} - > - - Send gratitude - -
    -
    - {!hasManagerEmail && ( - - Manager's email is required to send gratitude - - )} -
    +
    + { + if (hasManagerEmail) { + setGratitudeModal({ isOpen: true, action }); + } + }} + className={hasManagerEmail ? "text-pink-600" : "text-pink-300 cursor-not-allowed"} + disabled={!hasManagerEmail} + title={!hasManagerEmail ? "Manager's email is required to send gratitude" : ""} + > + + Send gratitude + +
    )} From 493da17b7127bab6bba1c7ecc4c22ff9831c282d Mon Sep 17 00:00:00 2001 From: Alexander Date: Sun, 23 Mar 2025 21:43:57 +0000 Subject: [PATCH 17/34] fix: simplified font color selection on verbTiles --- src/features/wizard/components/FilterBar.tsx | 12 ++++++------ src/features/wizard/components/VerbGrid.tsx | 7 ++++--- .../wizard/components/VerbSelector.tsx | 18 +++++++----------- 3 files changed, 17 insertions(+), 20 deletions(-) diff --git a/src/features/wizard/components/FilterBar.tsx b/src/features/wizard/components/FilterBar.tsx index 9de212f..fb20bf8 100644 --- a/src/features/wizard/components/FilterBar.tsx +++ b/src/features/wizard/components/FilterBar.tsx @@ -83,12 +83,12 @@ const FilterBar: React.FC = ({
    {/* CASE 1: No filter selected → show top-level categories in a grid */} {!currentCategory && ( -
    +
    {topCategories.map((cat) => (
    - + {cat.displayName} @@ -111,12 +111,12 @@ const FilterBar: React.FC = ({ {currentCategory && currentCategory.children && currentCategory.children.length > 0 && ( -
    +
    {currentCategory.children.map((child) => (
    - + {child.displayName} diff --git a/src/features/wizard/components/VerbGrid.tsx b/src/features/wizard/components/VerbGrid.tsx index fa9b147..815f94d 100644 --- a/src/features/wizard/components/VerbGrid.tsx +++ b/src/features/wizard/components/VerbGrid.tsx @@ -3,8 +3,8 @@ import React from 'react'; import { Button } from '@/components/ui/button'; import type { Verb, Category } from '@/types/entries'; -import { getContrastColor } from '@/lib/utils/colorUtils'; import { getVerbColor } from '@/lib/utils/categoryUtils'; +import { getContrastColor } from '@/lib/utils/colorUtils'; interface VerbGridProps { verbs: Verb[]; @@ -24,8 +24,8 @@ const VerbGrid: React.FC = ({
    {sortedVerbs.map((verb) => { const tileColor = getVerbColor(verb, rootCategory); - const isSelected = verb.id === selectedVerbId; + return ( ))}