Tutorial Working

This commit is contained in:
2025-03-15 18:23:04 -04:00
parent a842c24d0d
commit ea0018bae2
15 changed files with 517 additions and 29 deletions

View File

@@ -0,0 +1,17 @@
import { CallBackProps, Joyride, Props } from "react-joyride";
interface TutorialComponentProps extends Props {
updateFunction: () => void;
}
export default function TutorialComponent(props: TutorialComponentProps){
return Joyride({
...props,
callback: (callbackProps: CallBackProps) => {
if(callbackProps.type === "tour:end"){
props.updateFunction();
}
}
});
}

View File

@@ -33,8 +33,6 @@ export default function ProtectedNavLinks(){
path: "/logout"
});
console.log("is admin = " + isSiteAdmin(accountPermissions));
return (
<>

View File

@@ -3,6 +3,7 @@ import { HTMLProps, useState } from "react";
export interface Tab {
tabId?: string;
tabHeader: React.ReactNode;
headerClasses?: string;
tabContent: React.ReactNode;
@@ -39,6 +40,7 @@ export default function TabGroup(props: TabGroupProps){
{
tabs.map((tab, index) => (
<TabHeader
id={tab.tabId}
key={index}
tab={tab}
active={activeTab === index}
@@ -71,16 +73,19 @@ export default function TabGroup(props: TabGroupProps){
function TabHeader({
id,
tab,
onClick,
active
}:{
id?: string;
tab: Tab;
onClick: () => void;
active: boolean;
}){
return (
<div
id={id}
className={clsx(
tab.headerClasses,
"px-4 py-2 rounded-t-lg cursor-pointer whitespace-nowrap",

View File

@@ -1,4 +1,5 @@
import { Account } from "@/interface/Account";
import { AccountTutorialStatus } from "@/interface/AccountTutorialStatus";
import { RaidGroupPermissionType } from "@/interface/RaidGroup";
import { api } from "@/util/AxiosUtil";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
@@ -124,6 +125,41 @@ export function useGetAccountsByRaidGroupCount(raidGroupId: string, searchTerm?:
});
}
export function useGetTutorialsStatus(accountId: string | null){
return useQuery({
queryKey: ["tutorials", "account", accountId],
queryFn: async () => {
const response = await api.get(`/account/tutorial`);
if(response.status !== 200){
throw new Error("Failed to get tutorials status");
}
else if(response.data.errors){
throw new Error(response.data.errors.join(", "));
}
return response.data as AccountTutorialStatus;
},
enabled: !!accountId
});
}
export function useUpdateTutorialsStatus(){
return useMutation({
mutationKey: ["tutorials", "accounts"],
mutationFn: async (tutorials: AccountTutorialStatus) => {
const response = await api.put(`/account/tutorial`, tutorials);
if(response.status !== 200){
throw new Error("Failed to update tutorials status");
}
else if(response.data.errors){
throw new Error(response.data.errors.join(", "));
}
}
});
}
export function useForcePasswordReset(accountId: string){

View File

@@ -0,0 +1,15 @@
export enum TutorialStatus {
COMPLETED = "COMPLETED",
NOT_COMPLETED = "NOT_COMPLETED"
}
export interface AccountTutorialStatus {
accountTutorialStatusId?: string;
accountId: string;
gamesTutorialStatus: TutorialStatus;
gameTutorialStatus: TutorialStatus;
raidGroupsTutorialStatus: TutorialStatus;
raidGroupTutorialStatus: TutorialStatus;
instanceTutorialStatus: TutorialStatus;
}

View File

@@ -1,33 +1,39 @@
import DangerMessage from "@/components/message/DangerMessage";
import TabGroup, { Tab } from "@/components/tab/TabGroup";
import TutorialComponent from "@/components/TutorialComponent";
import { useGetGame } from "@/hooks/GameHooks";
import { TutorialStatus } from "@/interface/AccountTutorialStatus";
import { Game } from "@/interface/Game";
import { useAuth } from "@/providers/AuthProvider";
import GameCalendarDisplay from "@/ui/calendar/GameCalendarDisplay";
import GameHeader from "@/ui/game/GameHeader";
import GameClassDisplay from "@/ui/gameClass/GameClassDisplay";
import RaidGroupsByGameDisplay from "@/ui/raidGroup/RaidGroupsByGameDisplay";
import { gameTutorialSteps } from "@/util/TutorialUtil";
import { useEffect, useState } from "react";
import { Navigate, useParams } from "react-router";
export default function GamePage(){
const { gameId } = useParams();
const tabs: Tab[] = [
{
tabHeader: "Calendar",
tabContent: <GameCalendarDisplay gameId={gameId!}/>
},
{
tabHeader: "Raid Groups",
tabContent: <RaidGroupsByGameDisplay gameId={gameId!}/>
},
{
tabHeader: "Classes",
tabContent: <GameClassDisplay gameId={gameId!}/>
}
];
const [ game, setGame ] = useState<Game>();
const { tutorialsStatus, setTutorialsStatus } = useAuth();
const gameQuery = useGetGame(gameId ?? "", false);
const updateGameTutorialStatus = () => {
const newTutorialsStatus = { ...tutorialsStatus };
if(tutorialsStatus.gameTutorialStatus === TutorialStatus.COMPLETED){
newTutorialsStatus.gameTutorialStatus = TutorialStatus.NOT_COMPLETED;
}
else if(tutorialsStatus.gameTutorialStatus === TutorialStatus.NOT_COMPLETED){
newTutorialsStatus.gameTutorialStatus = TutorialStatus.COMPLETED;
}
setTutorialsStatus(newTutorialsStatus);
}
useEffect(() => {
if(gameQuery.status === "success"){
setGame(gameQuery.data);
@@ -42,7 +48,7 @@ export default function GamePage(){
}
else if(gameQuery.status === "error"){
return (
<div>Error</div>
<DangerMessage>Error: {gameQuery.error.message}</DangerMessage>
);
}
else if(gameQuery.status === "success" && gameQuery.data === undefined){
@@ -51,10 +57,39 @@ export default function GamePage(){
);
}
else if(game){
const tabs: Tab[] = [
{
tabId: "calendarTab",
tabHeader: "Calendar",
tabContent: <GameCalendarDisplay gameId={gameId!}/>
},
{
tabId: "raidGroupsTab",
tabHeader: "Raid Groups",
tabContent: <RaidGroupsByGameDisplay gameId={gameId!}/>
},
{
tabId: "gameClassesTab",
tabHeader: "Classes",
tabContent: <GameClassDisplay gameId={gameId!}/>
}
];
return (
<main
className="flex flex-col items-center justify-center"
>
{/* Tutorials */}
<TutorialComponent
steps={gameTutorialSteps}
run={tutorialsStatus.gameTutorialStatus === TutorialStatus.NOT_COMPLETED}
showProgress={true}
showSkipButton={true}
continuous={true}
disableScrolling={true}
updateFunction={updateGameTutorialStatus}
/>
<GameHeader
game={game}
/>

View File

@@ -1,11 +1,35 @@
import TutorialComponent from "@/components/TutorialComponent";
import { TutorialStatus } from "@/interface/AccountTutorialStatus";
import { useAuth } from "@/providers/AuthProvider";
import AllGamesDisplay from "@/ui/game/AllGamesDisplay";
import { gamesTutorialSteps } from "@/util/TutorialUtil";
export default function GamesPage(){
const { tutorialsStatus, setTutorialsStatus } = useAuth();
const updateGamesTutorialStatus = () => {
const newTutorialsStatus = { ...tutorialsStatus };
if(tutorialsStatus.gamesTutorialStatus === TutorialStatus.COMPLETED){
newTutorialsStatus.gamesTutorialStatus = TutorialStatus.NOT_COMPLETED;
}
else if(tutorialsStatus.gamesTutorialStatus === TutorialStatus.NOT_COMPLETED){
newTutorialsStatus.gamesTutorialStatus = TutorialStatus.COMPLETED;
}
setTutorialsStatus(newTutorialsStatus);
}
return (
<main
className="flex flex-col items-center justify-center gap-8 w-full"
className="flex flex-col items-center justify-center gap-y-8 w-full"
>
<TutorialComponent
steps={gamesTutorialSteps}
run={tutorialsStatus.gamesTutorialStatus === TutorialStatus.NOT_COMPLETED}
updateFunction={updateGamesTutorialStatus}
/>
<h1
className="text-4xl"
>

View File

@@ -1,6 +1,8 @@
import DangerMessage from "@/components/message/DangerMessage";
import TabGroup, { Tab } from "@/components/tab/TabGroup";
import TutorialComponent from "@/components/TutorialComponent";
import { useGetRaidGroup } from "@/hooks/RaidGroupHooks";
import { TutorialStatus } from "@/interface/AccountTutorialStatus";
import { RaidGroup } from "@/interface/RaidGroup";
import { useAuth } from "@/providers/AuthProvider";
import RaidGroupAccountsTab from "@/ui/account/RaidGroupAccountsTab";
@@ -12,17 +14,20 @@ import RaidGroupRequestTab from "@/ui/raidGroupRequest/RaidGroupRequestTab";
import RaidInstanceTab from "@/ui/raidInstance/RaidInstanceTab";
import RaidLayoutTab from "@/ui/raidLayout/RaidLayoutTab";
import { isRaidGroupAdmin } from "@/util/PermissionUtil";
import { raidGroupTutorialSteps } from "@/util/TutorialUtil";
import { useEffect, useState } from "react";
import { Navigate, useParams } from "react-router";
export default function RaidGroupPage(){
const { raidGroupId } = useParams();
const { accountPermissions, raidGroupPermissions } = useAuth();
const { accountPermissions, raidGroupPermissions, tutorialsStatus, setTutorialsStatus } = useAuth();
const [ raidGroup, setRaidGroup ] = useState<RaidGroup>();
const raidGroupQuery = useGetRaidGroup(raidGroupId ?? "", false);
useEffect(() => {
if(raidGroupQuery.status === "success"){
setRaidGroup(raidGroupQuery.data);
@@ -30,6 +35,18 @@ export default function RaidGroupPage(){
}, [ raidGroupQuery ]);
const updateRaidGroupTutorialStatus = () => {
const newTutorialsStatus = { ...tutorialsStatus };
if(tutorialsStatus.raidGroupTutorialStatus === TutorialStatus.COMPLETED){
newTutorialsStatus.raidGroupTutorialStatus = TutorialStatus.NOT_COMPLETED;
}
else if(tutorialsStatus.raidGroupTutorialStatus === TutorialStatus.NOT_COMPLETED){
newTutorialsStatus.raidGroupTutorialStatus = TutorialStatus.COMPLETED;
}
setTutorialsStatus(newTutorialsStatus);
}
if(raidGroupQuery.status === "pending"){
return (
<div>Loading...</div>
@@ -48,22 +65,27 @@ export default function RaidGroupPage(){
else if(raidGroup){
const tabs: Tab[] = [
{
tabId: "calendarTab",
tabHeader: "Calendar",
tabContent: <RaidGroupCalendarDisplay gameId={raidGroup?.gameId ?? ""} raidGroupId={raidGroupId!}/>
},
{
tabId: "peopleTab",
tabHeader: "People",
tabContent: <PersonTab raidGroup={raidGroup}/>
},
{
tabId: "classGroupsTab",
tabHeader: "Class Groups",
tabContent: <ClassGroupsTab raidGroup={raidGroup}/>
},
{
tabId: "raidLayoutsTab",
tabHeader: "Raid Layout",
tabContent: <RaidLayoutTab raidGroup={raidGroup}/>
},
{
tabId: "instancesTab",
tabHeader: "Raid Instances",
tabContent: <RaidInstanceTab raidGroup={raidGroup}/>
}
@@ -71,10 +93,12 @@ export default function RaidGroupPage(){
if(isRaidGroupAdmin(raidGroupId!, raidGroupPermissions, accountPermissions)){
tabs.push({
tabId: "usersTab",
tabHeader: "Users",
tabContent: <RaidGroupAccountsTab raidGroup={raidGroup}/>
});
tabs.push({
tabId: "requestsTab",
tabHeader: "Requests",
tabContent: <RaidGroupRequestTab raidGroup={raidGroup}/>
});
@@ -85,6 +109,15 @@ export default function RaidGroupPage(){
<main
className="flex flex-col items-center justify-center gap-y-8"
>
<TutorialComponent
steps={raidGroupTutorialSteps}
run={tutorialsStatus.raidGroupTutorialStatus === TutorialStatus.NOT_COMPLETED}
showProgress={true}
showSkipButton={true}
continuous={true}
disableScrolling={true}
updateFunction={updateRaidGroupTutorialStatus}
/>
<RaidGroupHeader
raidGroup={raidGroup}
/>

View File

@@ -1,15 +1,35 @@
import TutorialComponent from "@/components/TutorialComponent";
import { TutorialStatus } from "@/interface/AccountTutorialStatus";
import { useAuth } from "@/providers/AuthProvider";
import RaidGroupsByAccountDisplay from "@/ui/raidGroup/RaidGroupsByAccountDisplay";
import { raidGroupsTutorialSteps } from "@/util/TutorialUtil";
export default function RaidGroupsPage(){
const { accountId } = useAuth();
const { accountId, tutorialsStatus, setTutorialsStatus } = useAuth();
const updateRaidGroupsTutorialStatus = () => {
const newTutorialsStatus = { ...tutorialsStatus };
if(tutorialsStatus.raidGroupsTutorialStatus === TutorialStatus.COMPLETED){
newTutorialsStatus.raidGroupsTutorialStatus = TutorialStatus.NOT_COMPLETED;
}
else if(tutorialsStatus.raidGroupsTutorialStatus === TutorialStatus.NOT_COMPLETED){
newTutorialsStatus.raidGroupsTutorialStatus = TutorialStatus.COMPLETED;
}
setTutorialsStatus(newTutorialsStatus);
}
return (
<main
className="flex flex-col items-center justify-center gap-8"
>
<TutorialComponent
steps={raidGroupsTutorialSteps}
run={tutorialsStatus.raidGroupsTutorialStatus === TutorialStatus.NOT_COMPLETED}
updateFunction={updateRaidGroupsTutorialStatus}
/>
<h1
className="text-4xl"
>

View File

@@ -1,15 +1,32 @@
import PrimaryButton from "@/components/button/PrimaryButton";
import TutorialComponent from "@/components/TutorialComponent";
import { TutorialStatus } from "@/interface/AccountTutorialStatus";
import { useAuth } from "@/providers/AuthProvider";
import RaidInstanceLayoutProvider from "@/providers/RaidInstanceLayoutProvider";
import RaidInstanceCreatorUI from "@/ui/raidInstance/creator/RaidInstanceCreatorUI";
import { instanceTutorialSteps } from "@/util/TutorialUtil";
import { BsArrowLeft } from "react-icons/bs";
import { useNavigate, useParams } from "react-router";
export default function RaidInstancePage(){
const { raidGroupId, raidInstanceId } = useParams();
const { tutorialsStatus, setTutorialsStatus } = useAuth();
const navigator = useNavigate();
const updateGamesTutorialStatus = () => {
const newTutorialsStatus = { ...tutorialsStatus };
if(tutorialsStatus.instanceTutorialStatus === TutorialStatus.COMPLETED){
newTutorialsStatus.instanceTutorialStatus = TutorialStatus.NOT_COMPLETED;
}
else if(tutorialsStatus.instanceTutorialStatus === TutorialStatus.NOT_COMPLETED){
newTutorialsStatus.instanceTutorialStatus = TutorialStatus.COMPLETED;
}
setTutorialsStatus(newTutorialsStatus);
}
return (
<RaidInstanceLayoutProvider
raidGroupId={raidGroupId ?? ""}
@@ -18,6 +35,15 @@ export default function RaidInstancePage(){
<main
className="flex flex-col items-center justify-center"
>
<TutorialComponent
steps={instanceTutorialSteps}
run={tutorialsStatus.instanceTutorialStatus === TutorialStatus.NOT_COMPLETED}
showProgress={true}
showSkipButton={true}
continuous={true}
disableScrolling={true}
updateFunction={updateGamesTutorialStatus}
/>
{/* Back to Raid Group Button */}
<div
className="flex flex-row items-center justify-start w-full my-8"

View File

@@ -1,4 +1,6 @@
import { useGetTutorialsStatus, useUpdateTutorialsStatus } from "@/hooks/AccountHooks";
import { AccountPermission } from "@/interface/AccountPermission";
import { AccountTutorialStatus } from "@/interface/AccountTutorialStatus";
import { GamePermission } from "@/interface/GamePermission";
import { RaidGroupPermission } from "@/interface/RaidGroupPermission";
import { RaidGroupRequest } from "@/interface/RaidGroupRequest";
@@ -23,6 +25,8 @@ type AuthProviderState = {
raidGroupPermissions: RaidGroupPermission[];
gamePermissions: GamePermission[];
raidGroupRequests: RaidGroupRequest[];
tutorialsStatus: AccountTutorialStatus;
setTutorialsStatus: (tutorialsStatus: AccountTutorialStatus) => void;
}
const initialState: AuthProviderState = {
@@ -34,7 +38,9 @@ const initialState: AuthProviderState = {
accountPermissions: [],
raidGroupPermissions: [],
gamePermissions: [],
raidGroupRequests: []
raidGroupRequests: [],
tutorialsStatus: {} as AccountTutorialStatus,
setTutorialsStatus: () => null
}
const AuthContext = createContext<AuthProviderState>(initialState);
@@ -51,6 +57,11 @@ export function AuthProvider({
const [ raidGroupPermissions, setRaidGroupPermissions ] = useState<RaidGroupPermission[]>([]);
const [ gamePermissions, setGamePermissions ] = useState<GamePermission[]>([]);
const [ raidGroupRequests, setRaidGroupRequests ] = useState<RaidGroupRequest[]>([]);
const [ tutorialsStatus, setTutorialsStatus ] = useState<AccountTutorialStatus>({} as AccountTutorialStatus);
const tutorialsStatusQuery = useGetTutorialsStatus(accountId);
const { mutate: tutorialsStatusMutation } = useUpdateTutorialsStatus();
const fetchToken = useCallback(async () => {
@@ -112,18 +123,29 @@ export function AuthProvider({
fetchToken();
}, [ fetchToken ]);
//Update the tutorial status when fetched
useEffect(() => {
if(tutorialsStatusQuery.status === "success"){
setTutorialsStatus(tutorialsStatusQuery.data);
}
}, [ tutorialsStatusQuery.status, tutorialsStatusQuery.data ]);
const updateTutorialsStatus = useCallback((newTutorialsStatus: AccountTutorialStatus) => {
setTutorialsStatus(newTutorialsStatus);
tutorialsStatusMutation(newTutorialsStatus);
}, [ setTutorialsStatus, tutorialsStatusMutation ]);
const currentTokens = useMemo(() => ({
jwt,
setJwt,
expiration,
setExpiration,
jwt, setJwt,
expiration, setExpiration,
accountId,
accountPermissions,
raidGroupPermissions,
gamePermissions,
raidGroupRequests
}), [ jwt, setJwt, expiration, setExpiration, accountId, accountPermissions, raidGroupPermissions, gamePermissions, raidGroupRequests ]);
raidGroupRequests,
tutorialsStatus, setTutorialsStatus: updateTutorialsStatus
}), [ jwt, expiration, accountId, accountPermissions, raidGroupPermissions, gamePermissions, raidGroupRequests, tutorialsStatus, updateTutorialsStatus ]);
//TODO: Return a spinner while the first token is being fetched

View File

@@ -195,6 +195,7 @@ export default function RaidInstanceHeader(){
>
{
<PrimaryButton
id="saveInstanceButton"
onClick={saveRaidInstance}
>
Save

View File

@@ -42,6 +42,7 @@ export default function RaidInstanceCreatorTable({
</div>
{/* Buttons */}
<PrimaryButton
id="instanceAddRunButton"
onClick={addRun}
>
Add Run

View File

@@ -104,6 +104,7 @@ export default function RaidInstanceCreatorTableBody({
className="flex flex-row justify-center items-center cursor-pointer p-2 space-x-2"
>
<DangerButton
id={`removeRun${runIndex}`}
variant="ghost"
shape="square"
onClick={() => removeRun(runIndex)}
@@ -114,6 +115,7 @@ export default function RaidInstanceCreatorTableBody({
/>
</DangerButton>
<TertiaryButton
id={`copyDiscordString${runIndex}`}
variant="ghost"
shape="square"
onClick={() => copyDiscordStringToClipBoard(runIndex)}

253
src/util/TutorialUtil.ts Normal file
View File

@@ -0,0 +1,253 @@
import { Step } from "react-joyride";
const nextButtonStyles = {
backgroundColor: "#0000FF"
};
const backButtonStyles = {
color: "#0000FF"
}
export const gamesTutorialSteps: Array<Step> = [
{
target: "body",
content: "This is the games page. It allows you to select a game to view its details and Raid Groups.",
placement: "center",
styles: {
buttonNext: nextButtonStyles
}
}
];
export const gameTutorialSteps: Array<Step> = [
{
target: "body",
content: "This is the games page. From here you can see info about the game and groups that play this game.",
placement: "center",
styles: {
buttonNext: nextButtonStyles,
buttonBack: backButtonStyles
}
},
{
target: "#calendarTab",
content: "This is the Calendar tab. It is populated with game-wide events by Game Admins.",
placement: "bottom",
styles: {
buttonNext: nextButtonStyles,
buttonBack: backButtonStyles
}
},
{
target: "#raidGroupsTab",
content: "This tab displays all the Raid Groups associated with this game. " +
"If you already belong to a Raid Group you can go to tis Raid Group page. " +
"You can also request to join an existing Raid Group or create your own.",
placement: "bottom",
styles: {
buttonNext: nextButtonStyles,
buttonBack: backButtonStyles
}
},
{
target: "#gameClassesTab",
content: "This tab displays all the classes in the game. " +
"From here you can update, add, or delete information related to the classes.",
placement: "bottom",
styles: {
buttonNext: nextButtonStyles,
buttonBack: backButtonStyles
}
}
];
export const raidGroupsTutorialSteps: Array<Step> = [
{
target: "body",
content: "This is the Raid Groups page. " +
"This is where you can see all of the Raid Groups to which you belong. " +
"When you select one of the Group you will be taken to their Raid Group page.",
placement: "center",
styles: {
buttonNext: nextButtonStyles
}
}
];
export const raidGroupTutorialSteps: Array<Step> = [
{
target: "body",
content: "This is the Raid Group page. " +
"From here you can see everything that is happening in the Raid Group.",
placement: "center",
styles: {
buttonNext: nextButtonStyles
}
},
{
target: "#peopleTab",
content: "Start your Raid Group by adding the people who will be participating in your raids. " +
"By adding the person's Discord ID you will be able to copy raids directly from Raid Builder into Discord with the user tagged.",
placement: "bottom",
styles: {
buttonNext: nextButtonStyles
}
},
{
target: "#peopleTab",
content: "Once a person has been added you can add characters to that person. " +
"These characters can then be used to make a roster. " +
"In order to edit or delete a character you can click the person's name to go to the details page.",
placement: "bottom",
styles: {
buttonNext: nextButtonStyles
}
},
{
target: "body",
content: "This is all that is required to begin creating rosters. " +
"However, there are a few more steps you can take to make creating rosters faster and easier.",
placement: "center",
styles: {
buttonNext: nextButtonStyles
}
},
{
target: "#classGroupsTab",
content: "Class Groups allow you to define roles in a raid and determine which classes are capable of filling that role.",
placement: "bottom",
styles: {
buttonNext: nextButtonStyles
}
},
{
target: "#classGroupsTab",
content: "As an example:\n" +
"You could create a Class Group named \"Damage\" and include the classes \"Hunter\" and \"Rogue\"." +
"Then, during roster creation you can select the \"Damage\" role as required. " +
"This will automatically filter the characters so you only see Hunters and Rogues when filling that spot in the roster.",
placement: "bottom",
styles: {
buttonNext: nextButtonStyles
}
},
{
target: "#raidLayoutsTab",
content: "Raid Layouts allow you to save the makeup of a raid, in regards to which Class Groups are required. " +
"Whether this is a raid you run often, or the general makeup of a good group, " +
"having a Layout before you create a roster will automatically fill in the Class Groups section of the roster, " +
"meaning all characters are correctly filtered for each role from the start.",
placement: "bottom",
styles: {
buttonNext: nextButtonStyles
}
},
{
target: "#instancesTab",
content: "This tab holds all of the rosters that you have created. " +
"They can have distinct names and times, and you can even create multiple rosters within the same instance to facilitate instance farming.",
placement: "bottom",
styles: {
buttonNext: nextButtonStyles
}
},
{
target: "#calendarTab",
content: "The Calendar tab is where you can see all of the events that are happening in the Raid Group. " +
"These events include the events from the Game, as well as Instances and any other custom events that Raid Group admins have created.",
placement: "bottom",
styles: {
buttonNext: nextButtonStyles
}
},
{
target: "#usersTab",
content: "The users tab displays website accounts that have access to the Raid Group and let you control the permission level of each user.",
placement: "bottom",
styles: {
buttonNext: nextButtonStyles
}
},
{
target: "#requestsTab",
content: "The Requests tab shows users that have requested access to your Raid Group. " +
"Once you have reviewed the request you can deny it, or accept it and choose the user's permission level.",
placement: "bottom",
styles: {
buttonNext: nextButtonStyles
}
}
];
export const instanceTutorialSteps: Array<Step> = [
{
target: "body",
content: "This is the Raid Instance page. " +
"This is where you can setup rosters for a run.",
placement: "center",
styles: {
buttonNext: nextButtonStyles
}
},
{
target: "#selectRaidLayoutButton",
content: "To start you can pick an existing Raid Layout, which will automatically filter characters in each slot for you.",
placement: "bottom",
styles: {
buttonNext: nextButtonStyles
}
},
{
target: "#instanceTableHead",
content: "You can also create an adhoc raid by clicking one of the Any cells and selecting Class Groups directly.",
placement: "bottom",
styles: {
buttonNext: nextButtonStyles
}
},
{
target: "#instanceTableBody",
content: "Or you can begin by clicking None and selecting characters to fill each slot.",
placement: "bottom",
styles: {
buttonNext: nextButtonStyles
}
},
{
target: "#saveInstanceButton",
content: "You can save the Instance at any time. This will cause it to appear on the Instance and Calendar tabs. " +
"You can also come back and edit it later if needed.",
placement: "bottom",
styles: {
buttonNext: nextButtonStyles
}
},
{
target: "#instanceAddRunButton",
content: "Click this button to add another run to the instance.",
placement: "bottom",
styles: {
buttonNext: nextButtonStyles
}
},
{
target: "#removeRun0",
content: "Click this button to remove this run from the instance.",
placement: "bottom",
styles: {
buttonNext: nextButtonStyles
}
},
{
target: "#copyDiscordString0",
content: "Click this button to copy a string to your clipboard that is ready to be pasted into Discord.",
placement: "bottom",
styles: {
buttonNext: nextButtonStyles
}
}
];