> ## Documentation Index
> Fetch the complete documentation index at: https://developer.box.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Scaffold application code (Slack integration)

export const ProgressBar = ({pages = [], ...props}) => {
  const [currentStep, setCurrentStep] = useState(0);
  const [isDarkMode, setIsDarkMode] = useState(false);
  useEffect(() => {
    const checkDarkMode = () => {
      const isDark = document.documentElement.classList.contains('dark');
      console.log('ProgressBar - isDarkMode:', isDark);
      setIsDarkMode(isDark);
    };
    checkDarkMode();
    const observer = new MutationObserver(() => {
      checkDarkMode();
    });
    observer.observe(document.documentElement, {
      attributes: true,
      attributeFilter: ['class']
    });
    return () => {
      observer.disconnect();
    };
  }, []);
  useEffect(() => {
    if (pages.length > 0) {
      const currentPath = window.location.pathname;
      const stepIndex = pages.findIndex(page => {
        const pagePath = page.startsWith('/') ? page : `/${page}`;
        return currentPath.endsWith(pagePath) || currentPath.includes(pagePath);
      });
      if (stepIndex !== -1) {
        setCurrentStep(stepIndex + 1);
      }
    }
  }, [pages]);
  if (!pages || pages.length === 0) {
    return null;
  }
  const step = currentStep;
  const total = pages.length;
  console.log('ProgressBar - Rendering with isDarkMode:', isDarkMode);
  const progressBarContainerStyle = {
    width: '100%',
    marginBottom: '32px',
    display: 'flex',
    alignItems: 'center',
    gap: '16px'
  };
  const stepsContainerStyle = {
    display: 'flex',
    alignItems: 'center',
    gap: '8px',
    flexShrink: 0
  };
  const progressBarTrackStyle = {
    flex: 1,
    height: '22px',
    backgroundColor: 'rgba(169, 210, 244, 0.06)',
    border: isDarkMode ? '1px solid rgba(230, 241, 247, 0.67)' : '1px solid #e3ecf3',
    borderRadius: '4px',
    overflow: 'hidden',
    position: 'relative'
  };
  const progressBarFillStyle = {
    height: '100%',
    backgroundColor: 'rgba(113, 192, 248, 0.23)',
    width: `${step / total * 100}%`,
    transition: 'width 0.3s ease'
  };
  const getStepStyle = (stepNumber, isActive) => {
    if (isDarkMode) {
      return {
        width: '22px',
        height: '22px',
        borderRadius: '4px',
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'center',
        fontSize: '12px',
        fontWeight: '600',
        position: 'relative',
        zIndex: 1,
        transition: 'all 0.3s ease',
        backgroundColor: isActive ? 'rgba(113, 192, 248, 0.23)' : 'transparent',
        color: isActive ? '#60a5fa' : '#a0aec0',
        border: isActive ? '1px solid #e3ecf3' : '1px solid #e0e6eb',
        cursor: 'pointer',
        textDecoration: 'none'
      };
    }
    if (isActive) {
      return {
        width: '22px',
        height: '22px',
        borderRadius: '4px',
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'center',
        fontSize: '12px',
        fontWeight: '600',
        position: 'relative',
        zIndex: 1,
        transition: 'all 0.3s ease',
        backgroundColor: 'rgba(169, 210, 244, 0.32)',
        color: '#374151',
        border: '1px solid #e1eef8',
        cursor: 'pointer',
        textDecoration: 'none'
      };
    }
    return {
      width: '22px',
      height: '22px',
      borderRadius: '4px',
      display: 'flex',
      alignItems: 'center',
      justifyContent: 'center',
      fontSize: '12px',
      fontWeight: '600',
      position: 'relative',
      zIndex: 1,
      transition: 'all 0.3s ease',
      backgroundColor: '#fbfbfb',
      color: '#9ca3af',
      border: '1px solid #e3ecf3',
      cursor: 'pointer',
      textDecoration: 'none'
    };
  };
  return <div style={progressBarContainerStyle} {...props}>
      <div style={stepsContainerStyle}>
        {Array.from({
    length: total
  }, (_, index) => {
    const stepNumber = index + 1;
    const pageIndex = index;
    const pagePath = pages[pageIndex];
    const fullPath = pagePath.startsWith('/') ? pagePath : `/${pagePath}`;
    const isActive = stepNumber === step;
    return <a key={stepNumber} href={fullPath} style={getStepStyle(stepNumber, isActive)}>
              {stepNumber}
            </a>;
  })}
      </div>
      <div style={progressBarTrackStyle}>
        <div style={progressBarFillStyle}></div>
      </div>
    </div>;
};

export const ChoiceDebug = ({option}) => {
  const [currentValue, setCurrentValue] = useState(null);
  const [allState, setAllState] = useState({});
  const [isDarkMode, setIsDarkMode] = useState(false);
  useEffect(() => {
    const updateState = () => {
      if (window.choiceStateManager) {
        setCurrentValue(window.choiceStateManager.getValue(option));
        setAllState(window.choiceStateManager.getState());
      }
    };
    updateState();
    const unsubscribe = window.listenToChoice?.(option, updateState) || (() => {});
    const handleGlobalUpdate = () => updateState();
    window.addEventListener("choiceStateUpdate", handleGlobalUpdate);
    return () => {
      unsubscribe();
      window.removeEventListener("choiceStateUpdate", handleGlobalUpdate);
    };
  }, [option]);
  useEffect(() => {
    const checkDarkMode = () => {
      if (document.documentElement.classList.contains("dark")) {
        setIsDarkMode(true);
      } else if (document.documentElement.classList.contains("light")) {
        setIsDarkMode(false);
      } else {
        setIsDarkMode(window.matchMedia("(prefers-color-scheme: dark)").matches);
      }
    };
    checkDarkMode();
    const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
    const handleMediaChange = e => {
      if (!document.documentElement.classList.contains("dark") && !document.documentElement.classList.contains("light")) {
        setIsDarkMode(e.matches);
      }
    };
    mediaQuery.addEventListener("change", handleMediaChange);
    const observer = new MutationObserver(() => {
      checkDarkMode();
    });
    observer.observe(document.documentElement, {
      attributes: true,
      attributeFilter: ["class"]
    });
    return () => {
      mediaQuery.removeEventListener("change", handleMediaChange);
      observer.disconnect();
    };
  }, []);
  return <div style={{
    padding: "10px",
    backgroundColor: isDarkMode ? "#2d3748" : "#f5f5f5",
    border: isDarkMode ? "1px solid #4a5568" : "1px solid #ddd",
    borderRadius: "4px",
    fontSize: "12px",
    fontFamily: "monospace",
    marginTop: "20px",
    color: isDarkMode ? "#e2e8f0" : "inherit"
  }}>
      <strong>Choice Debug:</strong>
      <br />
      Option: {option}
      <br />
      Current Value: {currentValue || "undefined"}
      <br />
      All State: {JSON.stringify(allState, null, 2)}
    </div>;
};

export const Observe = ({option, value, children, ...props}) => {
  const [shouldShow, setShouldShow] = useState(false);
  useEffect(() => {
    const updateVisibility = () => {
      const matches = window.matchesChoiceValues?.(option, value) || false;
      setShouldShow(matches);
    };
    updateVisibility();
    const unsubscribe = window.listenToChoice?.(option, updateVisibility) || (() => {});
    return unsubscribe;
  }, [option, value]);
  if (!shouldShow) {
    return null;
  }
  return <div {...props}>{children}</div>;
};

export const Trigger = ({option, value, children, ...props}) => {
  const handleClick = () => {
    window.triggerChoice?.(option, value);
  };
  return <div onClick={handleClick} style={{
    cursor: "pointer"
  }} {...props}>
      {children}
    </div>;
};

export const Grid = ({columns = 2, compact = false, children, ...props}) => {
  const gridStyles = {
    display: "grid",
    gridTemplateColumns: `repeat(${columns}, 1fr)`,
    gap: compact ? "8px" : "16px",
    marginBottom: compact ? "10px" : "20px"
  };
  return <div style={gridStyles} {...props}>
      {children}
    </div>;
};

export const Choice = ({option, value, color = "", unset = false, lazy = false, children, ...props}) => {
  const [shouldShow, setShouldShow] = useState(false);
  const [hasEverShown, setHasEverShown] = useState(false);
  const [isDarkMode, setIsDarkMode] = useState(false);
  useEffect(() => {
    const updateVisibility = () => {
      const hasOptionValue = window.hasChoiceValue?.(option) || false;
      const matchesValue = window.matchesChoiceValues?.(option, value) || false;
      let show = false;
      if (unset && !hasOptionValue) {
        show = true;
      } else if (!unset && matchesValue) {
        show = true;
      }
      setShouldShow(show);
      if (show && !hasEverShown) {
        setHasEverShown(true);
      }
    };
    updateVisibility();
    const unsubscribe = window.listenToChoice?.(option, updateVisibility) || (() => {});
    return unsubscribe;
  }, [option, value, unset, hasEverShown]);
  useEffect(() => {
    const checkDarkMode = () => {
      if (document.documentElement.classList.contains("dark")) {
        setIsDarkMode(true);
      } else if (document.documentElement.classList.contains("light")) {
        setIsDarkMode(false);
      } else {
        setIsDarkMode(window.matchMedia("(prefers-color-scheme: dark)").matches);
      }
    };
    checkDarkMode();
    const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
    const handleMediaChange = e => {
      if (!document.documentElement.classList.contains("dark") && !document.documentElement.classList.contains("light")) {
        setIsDarkMode(e.matches);
      }
    };
    mediaQuery.addEventListener("change", handleMediaChange);
    const observer = new MutationObserver(() => {
      checkDarkMode();
    });
    observer.observe(document.documentElement, {
      attributes: true,
      attributeFilter: ["class"]
    });
    return () => {
      mediaQuery.removeEventListener("change", handleMediaChange);
      observer.disconnect();
    };
  }, []);
  const getColorStyles = () => {
    const baseStyles = {
      border: isDarkMode ? "1px dashed #4a5568" : "1px dashed #e1e5e9",
      padding: "20px",
      marginBottom: "20px",
      borderRadius: "8px",
      backgroundColor: isDarkMode ? "#1a202c" : "#ffffff"
    };
    const colorMap = {
      green: {
        light: {
          backgroundColor: "#d4edda",
          borderColor: "#28a745"
        },
        dark: {
          backgroundColor: "#1a3a2a",
          borderColor: "#66bb6a"
        }
      },
      red: {
        light: {
          backgroundColor: "#f8d7da",
          borderColor: "#dc3545"
        },
        dark: {
          backgroundColor: "#3a1a1a",
          borderColor: "#ef5350"
        }
      },
      blue: {
        light: {
          backgroundColor: "#d1ecf1",
          borderColor: "#0c5460"
        },
        dark: {
          backgroundColor: "#1a2a3a",
          borderColor: "#42a5f5"
        }
      },
      none: {
        backgroundColor: "transparent",
        padding: "0",
        margin: "0",
        border: "none"
      }
    };
    const colorStyles = color !== "none" ? colorMap[color]?.[isDarkMode ? "dark" : "light"] || ({}) : colorMap.none;
    return {
      ...baseStyles,
      ...colorStyles
    };
  };
  if (lazy && !hasEverShown && !shouldShow) {
    return null;
  }
  return <div style={{
    ...getColorStyles(),
    display: shouldShow ? "block" : "none"
  }} className="choice-content" {...props}>
      {children}
    </div>;
};

export const Choose = ({option, value, color = "", children, ...props}) => {
  const [isSelected, setIsSelected] = useState(false);
  const [hasOptionTriggered, setHasOptionTriggered] = useState(false);
  const [isDarkMode, setIsDarkMode] = useState(false);
  useEffect(() => {
    const currentValue = window.getChoiceValue?.(option);
    const optionTriggered = window.hasChoiceValue?.(option) || false;
    setIsSelected(currentValue === value);
    setHasOptionTriggered(optionTriggered);
    const unsubscribe = window.listenToChoice?.(option, newValue => {
      setIsSelected(newValue === value);
      setHasOptionTriggered(true);
    }) || (() => {});
    return unsubscribe;
  }, [option, value]);
  useEffect(() => {
    const checkDarkMode = () => {
      if (document.documentElement.classList.contains("dark")) {
        setIsDarkMode(true);
      } else if (document.documentElement.classList.contains("light")) {
        setIsDarkMode(false);
      } else {
        setIsDarkMode(window.matchMedia("(prefers-color-scheme: dark)").matches);
      }
    };
    checkDarkMode();
    const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
    const handleMediaChange = e => {
      if (!document.documentElement.classList.contains("dark") && !document.documentElement.classList.contains("light")) {
        setIsDarkMode(e.matches);
      }
    };
    mediaQuery.addEventListener("change", handleMediaChange);
    const observer = new MutationObserver(() => {
      checkDarkMode();
    });
    observer.observe(document.documentElement, {
      attributes: true,
      attributeFilter: ["class"]
    });
    return () => {
      mediaQuery.removeEventListener("change", handleMediaChange);
      observer.disconnect();
    };
  }, []);
  const handleClick = () => {
    window.triggerChoice?.(option, value);
  };
  const handleKeyDown = e => {
    if (e.key === "Enter" || e.key === " ") {
      e.preventDefault();
      handleClick();
    }
  };
  const getColorStyles = () => {
    const baseStyles = {
      border: isDarkMode ? "1px dashed #4a5568" : "1px dashed #e1e5e9",
      cursor: "pointer",
      padding: "20px",
      position: "relative",
      backgroundColor: isDarkMode ? "#2d3748" : "#f8f9fa",
      outline: "none",
      height: "100%",
      borderRadius: "8px",
      transition: "all 0.2s ease",
      display: "flex",
      flexDirection: "column"
    };
    const colorMap = {
      green: {
        light: {
          backgroundColor: "#d4edda",
          borderColor: "#28a745"
        },
        dark: {
          backgroundColor: "#1a3a2a",
          borderColor: "#66bb6a"
        }
      },
      red: {
        light: {
          backgroundColor: "#f8d7da",
          borderColor: "#dc3545"
        },
        dark: {
          backgroundColor: "#3a1a1a",
          borderColor: "#ef5350"
        }
      },
      blue: {
        light: {
          backgroundColor: "#d1ecf1",
          borderColor: "#0c5460"
        },
        dark: {
          backgroundColor: "#1a2a3a",
          borderColor: "#42a5f5"
        }
      }
    };
    const colorStyles = colorMap[color]?.[isDarkMode ? "dark" : "light"] || ({});
    if (isSelected) {
      return {
        ...baseStyles,
        ...colorStyles,
        borderStyle: "solid",
        borderWidth: "3px",
        borderColor: colorStyles.borderColor || (isDarkMode ? "#42a5f5" : "#0061d5"),
        backgroundColor: colorStyles.backgroundColor || (isDarkMode ? "#1a2a3a" : "#e3f2fd"),
        boxShadow: isDarkMode ? "0 2px 8px rgba(66, 165, 245, 0.3)" : "0 2px 8px rgba(0, 97, 213, 0.3)",
        transform: "scale(1.02)"
      };
    }
    if (hasOptionTriggered && !isSelected) {
      return {
        ...baseStyles,
        ...colorStyles,
        opacity: 0.5
      };
    }
    return {
      ...baseStyles,
      ...colorStyles
    };
  };
  const iconStyles = {
    float: "left",
    position: "relative",
    top: "2px",
    marginRight: "12px",
    width: "20px",
    height: "20px",
    color: isSelected ? isDarkMode ? "#42a5f5" : "#0061d5" : isDarkMode ? "#a0aec0" : "#666"
  };
  return <div onClick={handleClick} style={getColorStyles()} tabIndex={0} onKeyDown={handleKeyDown} {...props}>
      <div style={iconStyles}>
        {isSelected ? <svg viewBox="0 0 24 24" fill="currentColor" style={{
    width: "100%",
    height: "100%"
  }}>
            <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z" />
          </svg> : <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" style={{
    width: "100%",
    height: "100%"
  }}>
            <circle cx="12" cy="12" r="10" />
          </svg>}
      </div>
      <div style={{
    flex: 1
  }} className="choose-content">
        {children}
      </div>
    </div>;
};

export const Link = ({href, children, className, ...props}) => {
  const localizedHref = localizeLink(href);
  return <a href={localizedHref} className={className} {...props}>
      {children}
    </a>;
};

<ProgressBar
  pages={[
                      "guides/collaborations/connect-slack-to-group-collabs/configure-slack",
                      "guides/collaborations/connect-slack-to-group-collabs/configure-box",
                      "guides/collaborations/connect-slack-to-group-collabs/scaffold-application-code",
                      "guides/collaborations/connect-slack-to-group-collabs/handle-slack-events",
                      "guides/collaborations/connect-slack-to-group-collabs/connect-box-functions",
                      "guides/collaborations/connect-slack-to-group-collabs/test-bot"
                    ]}
/>

With our Slack and Box applications configured, we're ready to write the code
for our application which will listen for incoming slash commands and events
from Slack.

This application will be split up into three parts:

* Set up the initial application skeleton and configuration information.
* Set up the handlers for Slack events and slash commands.
* Connect those handlers to the required functions in Box.

## Add dependencies and scaffold code

Let's start by scaffolding the files and minimal code needed to
run the application.

<Choice option="programming.platform" value="node" color="none">
  * Either create a new local directory for your application or load the existing code created for the Slack event URL challenge from <Link href="/guides/collaborations/connect-slack-to-group-collabs/configure-slack">step 1</Link>.
  * Create a new `package.json` file, or update the existing one, inside the local directory, open it in your preferred editor, copy / paste the following into it, and save / exit the file.

  ```json theme={null}
  {
    "name": "box-slack",
    "version": "1.0.0",
    "description": "Box Slack integration",
    "main": "process.js",
    "scripts": {
      "test": "echo \"Error: no test specified\" && exit 1",
      "start": "node process.js"
    },
    "author": "Box",
    "license": "ISC",
    "dependencies": {
      "axios": "^0.19.2",
      "body-parser": "^1.19.0",
      "box-node-sdk": "^1.33.0",
      "express": "^4.17.1"
    }
  }
  ```

  * Run `npm install` from the terminal / console to install the dependencies.
  * Create two files, `process.js` and `slackConfig.json` in the local directory.
  * Open `slackConfig.json` and save the following default configuration.

  ```json theme={null}
  {
    "verificationToken": "TOKEN",
    "botToken": "TOKEN"
  }
  ```

  * There are two values above that will need to be replaced with details from your Slack application. Replace the `TOKEN` strings with the appropriate values.
    * `verificationToken`: Load up your Slack application configuration page. Within the **Basic Information** page, scroll down to the **App Credentials** section. The **Verification Token** string will be available there.
    * `botToken`: Within your Slack application, go to the **OAuth & Permissions** page. The **Bot User OAuth Access Token** string is available at the top and was auto-populated once the bot was added to your Slack workspace.
  * Open up the blank `process.js` file, copy and paste the following code, and save the file.

  ```js theme={null}
  const box = require("box-node-sdk");

  const slackConfig = require("./slackConfig.json");
  const boxConfig = require("./boxConfig.json");

  const express = require("express");
  const app = express();
  const axios = require("axios");
  const util = require("util");

  app.use(express.urlencoded({ extended: true }));
  app.use(express.json());

  // INSTANTIATE BOX CLIENT

  app.post('/event', (req, res) => {
      //HANDLE INCOMING EVENTS
  });

  const handler = (() => {
      function process(res, data) {
          // PROCESS EVENTS
      }

      function processUser(user, event, channel) {
          // PROCESS USER ADD / REMOVE REQUEST
      }

      function addGroupUser(groupId, email) {
          // ADD USER TO BOX GROUP
      }

      function removeGroupUser(groupId, email) {
          // REMOVE USER FROM BOX GROUP
      }

      function processContent(user, channel, type, fid) {
          // COLLABORATE CONTENT WITH GROUP
      }

      function processSlackChannel(channel, gid) {
          // ADD ALL SLACK CHANNEL USERS TO GROUP
      }

      function getSlackUser(userId, _callback) {
          // GET SLACK USER PROFILE
      }

      function getGroupId(groupName, _callback) {
          // GET AND CREATE BOX GROUP
      }

      return {
          process
      };
  })();

  const port = process.env.PORT || 3000;
  app.listen(port, function(err) {
    console.log("Server listening on PORT", port);
  });
  ```

  This code contains all of the main functions that will be needed to handle and
  process the communication between Slack and Box. From top to bottom, the
  functions are:

  * `/event` handler: Captures all incoming Slack traffic, verifies the content, and routes them to the `process` function.
  * `process`: Parses the Slack event and routes the event to either Box group processing (user channel events) or to add Box content to the group (slash commands).
  * `processUser`: Handles user events, either adding or removing a user from a Box group by routing to the appropriate functions.
  * `addGroupUser`: Adds a user to a Box group.
  * `removeGroupUser`: Removes a user from a Box group.
  * `processContent`: Collaborates Box content with the Box group.
  * `processSlackChannel`: Adds all Slack channel users to a Box group.
  * `getSlackUser`: Utility function to fetch a Slack user's profile from their Slack user ID.
  * `getGroupId`: Utility function to fetch a Box group ID from a Box group name.
</Choice>

<Choice option="programming.platform" value="java" color="none">
  A configuration file needs to be created to house our Slack credentials and
  URLs.

  * Within your `src/main/java` path, where your `Application.java` file is located, create a new Java class file named `slackConfig.java`.
  * Open the file and save the following into it.

  ```java theme={null}
  package com.box.slack.box;

  public class slackConfig {
      public static String verificationToken = "TOKEN";
      public static String botToken = "TOKEN";
      public static String slackApiUrl = "https://slack.com/api";
  }
  ```

  * There are two values above that will need to be replaced with details from your Slack application. Replace the `TOKEN` strings with the appropriate values.
    * `verificationToken`: Load up your Slack application configuration page. Within the **Basic Information** page, scroll down to the **App Credentials** section. The **Verification Token** string will be available there.
    * `botToken`: Within your Slack application, go to the **OAuth & Permissions** page. The **Bot User OAuth Access Token** string is available at the top and was auto-populated once the bot was added to your Slack workspace.
  * Open up the `Application.java` file that was created for the previous Slack event challenge setup and replace the content of the file with the following.

  ```java theme={null}
  package com.box.slack.box;

  import java.io.BufferedReader;
  import java.io.FileReader;
  import java.io.InputStreamReader;
  import java.io.Reader;
  import java.io.UnsupportedEncodingException;
  import java.net.HttpURLConnection;
  import java.net.URL;
  import java.nio.charset.StandardCharsets;
  import java.util.Iterator;

  import javax.servlet.http.HttpServletResponse;

  import org.jose4j.json.internal.json_simple.JSONArray;
  import org.jose4j.json.internal.json_simple.JSONObject;
  import org.jose4j.json.internal.json_simple.parser.JSONParser;
  import org.springframework.boot.SpringApplication;
  import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
  import org.springframework.http.MediaType;
  import org.springframework.scheduling.annotation.Async;
  import org.springframework.web.bind.annotation.PostMapping;
  import org.springframework.web.bind.annotation.RequestBody;
  import org.springframework.web.bind.annotation.RequestHeader;
  import org.springframework.web.bind.annotation.ResponseBody;
  import org.springframework.web.bind.annotation.RestController;

  import com.box.sdk.BoxAPIConnection;
  import com.box.sdk.BoxCollaborator;
  import com.box.sdk.BoxCollaboration;
  import com.box.sdk.BoxConfig;
  import com.box.sdk.BoxDeveloperEditionAPIConnection;
  import com.box.sdk.BoxFile;
  import com.box.sdk.BoxFolder;
  import com.box.sdk.BoxGroup;
  import com.box.sdk.BoxGroupMembership;
  import com.box.sdk.BoxUser;

  @RestController
  @EnableAutoConfiguration
  public class Application extends slackConfig {
      private Reader fileReader;
      private BoxConfig boxConfig;
      private BoxAPIConnection boxAPI;

      @PostMapping("/event")
      @ResponseBody
      public void handleEvent(@RequestBody String data, @RequestHeader("Content-Type") String contentType, HttpServletResponse response) throws Exception {
          // HANDLE EVENTS
      }

      @Async
      public void processEvent(String data) throws Exception {
          // VERIFY EVENTS
      }

      public void process(JSONObject inputJSON) throws Exception {
          // PROCESS EVENTS
      }

      public void processUser(JSONObject userResponse, String event, String channel) throws Exception {
          // PROCESS USER ADD / REMOVE REQUEST
      }

      public void addGroupUser(String groupId, String userEmail) {
          // ADD USER TO BOX GROUP
      }

      public void removeGroupUser(String groupId, String userEmail) {
          // REMOVE USER FROM BOX GROUP
      }

      public void processContent(JSONObject userResponse, String channel, String fType, String fId) {
          // COLLABORATE CONTENT WITH GROUP
      }

      public void processSlackChannel(String channel, String groupId) throws Exception {
          // ADD ALL SLACK CHANNEL USERS TO GROUP
      }

      public JSONObject getSlackUser(String userId) throws Exception {
          // GET SLACK USER PROFILE
      }

      public String getGroupId(String groupName) {
          // GET AND CREATE BOX GROUP
      }

      public JSONObject sendGETRequest(String reqURL) throws Exception {
          StringBuffer response = new StringBuffer();

          URL obj = new URL(reqURL);
          HttpURLConnection httpURLConnection = (HttpURLConnection) obj.openConnection();
          httpURLConnection.setRequestMethod("GET");

          int responseCode = httpURLConnection.getResponseCode();
          if (responseCode == HttpURLConnection.HTTP_OK) {
              BufferedReader in = new BufferedReader(new InputStreamReader(httpURLConnection.getInputStream()));
              String inputLine;

              while ((inputLine = in .readLine()) != null) {
                  response.append(inputLine);
              } in .close();
          } else {
              System.err.println("GET request failed");
          }

          Object dataObj = new JSONParser().parse(response.toString());
          JSONObject outputJSON = (JSONObject) dataObj;

          return outputJSON;
      }

      public static void main(String[] args) {
          SpringApplication.run(Application.class, args);
      }
  }
  ```

  This code contains all of the main functions that will be needed to handle and
  process the communication between Slack and Box. From top to bottom, the
  functions are:

  * `handleEvent`: Captures all incoming Slack traffic and responds with an HTTP 200 response to notify Slack that the event was received. Since slash commands will transmit their payload as `application/x-www-form-urlencoded` rather than `application/json`, we convert those payloads to JSON objects to standardize the input.
  * `processEvent`: Verifies that the event is from Slack, instantiates a Box client, and routes for processing.
  * `process`: Parses the Slack event and routes to either Box group processing (user channel events) or to add Box content to the group (slash commands).
  * `processUser`: Handles user event requirements to either add or remove a user to a Box group by routing to the appropriate functions.
  * `addGroupUser`: Adds a user to a Box group.
  * `removeGroupUser`: Removes a user from a Box group.
  * `processContent`: Collaborates Box content with the Box group.
  * `processSlackChannel`: Adds all Slack channel users to a Box group.
  * `getSlackUser`: Utility function to fetch a Slack user profile from a Slack user ID.
  * `getGroupId`: Utility function to fetch a Box group ID from a Box group name.
  * `sendGETRequest`: Utility function to send an HTTP GET request.
</Choice>

<Choice option="programming.platform" unset color="none">
  <Danger>
    **Incomplete previous step**
    Please select a preferred language / framework in step 1 to get started.
  </Danger>
</Choice>

## Summary

* You created a minimal application scaffold, and provided the basic configuration details.
* You installed all the project dependencies.

<Observe option="programming.platform" value="node,java">
  <Next>I have my local application set up</Next>
</Observe>
