DEV Community

Cover image for #30DaysOfAppwrite : Using Team Invites
Christy Jacob for Appwrite

Posted on • Edited on

#30DaysOfAppwrite : Using Team Invites

Intro

#30DaysOfAppwrite is a month long event focused at giving developers a walk through of all of Appwrite's features, starting from the basics to more advanced features like Cloud Functions! Alongside we will also be building a fully featured Medium clone to demonstrate how these
concepts can be applied when building a real world app. We also have some exciting prizes for developers who follow along with us!

Using Team Invites

Welcome to Day 14 ๐Ÿ‘‹ . Yesterday, we talked in depth about the teams API and the conventions of creating team permissions in Appwrite. We will build upon yesterday's concept's to add some cool features to our demo app.

We will incorporate the following features into our demo app in this article.

  1. Create Teams
  2. List User's Teams
  3. Delete Team
  4. Get Team by ID
  5. Get members of a team
  6. Add a new team member
  7. Update Membership status
  8. Remove a user from a team

We will be creating three new routes in our project.

  1. A /profile/:id/teams route to allow a user to see all the teams they're part of and also create new teams. This route will implement features [1,2,3]
  2. A /team/:id route that will display details of a particular team ID and allow users to manage members of the team. This route will implement features [3,4,5,6,8]
  3. An /acceptMembership route that will enable a new team member to accept a team invite. This route will implement feature [7]

Setup

So let's get started. In src/App.svelte create three new routes.

import Team from "./routes/Team.svelte";
import Teams from "./routes/Teams.svelte";
import AcceptMembership from "./routes/AcceptMembership.svelte";

const routes = {
    ...
    "/profile/:id/teams" : Teams,
    "/team/:id" : Team,
    "/acceptMembership": AcceptMembership,
    ...
};
Enter fullscreen mode Exit fullscreen mode

Head over to src/appwrite.js and add the following functions:

...

fetchUserTeams: () => sdk.teams.list(),
createTeam: name => sdk.teams.create('unique()', name),
deleteTeam: id => sdk.teams.delete(id),
getTeam: id => sdk.teams.get(id),
getMemberships: teamId => sdk.teams.getMemberships(teamId),
createMembership: (teamId, email, roles, url, name) =>
    sdk.teams.createMembership(teamId, email, roles, url, name),
updateMembership: (teamId, inviteId, userId, secret) =>
    sdk.teams.updateMembershipStatus(teamId, inviteId, userId, secret),
deleteMembership: (teamId, inviteId) =>
    sdk.teams.deleteMembership(teamId, inviteId)
...
Enter fullscreen mode Exit fullscreen mode

In src/lib/Navigation.svelte we will create a link to the main /profile/:id/teams route.

...
{#if $state.user}
    <a href={`/profile/${$state.user.$id}`} use:link>{$state.user.name}</a>
    <a href={`/profile/${$state.user.$id}/teams`} use:link>My Teams</a>
    <a href="/logout" use:link>Logout</a>
{:else}
...
Enter fullscreen mode Exit fullscreen mode

Create a page to display all of the user's teams

Create a file src/routes/Teams.svelte. This is where the user can view all of their teams and create new teams. Add the following code in the <script> section.

<script>
  import { link } from "svelte-spa-router";
  import Avatar from "../lib/Avatar.svelte";
  import Loading from "../lib/Loading.svelte";
  import { api } from "../appwrite";
  export let params = {};

  let name;

  const fetchUser = () => api.fetchUser(params.id);
  const getAvatar = (name) => api.getAvatar(name);
  const fetchTeams = () => api.fetchUserTeams().then((r) => r.teams);
  const createTeam = (name) => api.createTeam(name);
  const deleteTeam = (id) => api.deleteTeam(id);
  let all = Promise.all([fetchUser(), fetchTeams()]);
</script>
Enter fullscreen mode Exit fullscreen mode

Let's now write some basic markup:

<section>
    {#await all}
        <Loading />
    {:then [author, teams]}
        <section class="author">
            <Avatar src={getAvatar(author.name)} />
            <h3>{author.name}</h3>
        </section>
        <section>
            <h1>My Teams</h1>
            <ul>
                {#each teams as team}
                    <li>
                        <a href={`/team/${team.$id}`} use:link>{team.name}</a>
                        <button
                            on:click={async () => {
                                await deleteTeam(team["$id"]);
                                all = Promise.all([
                                    author,
                                    fetchTeams(),
                                ]);
                                console.log("Deleted team", team["$id"]);
                            }}>โŒ</button>
                    </li>
                {/each}
            </ul>
        </section>

        <section>
            <h1>Create Team</h1>
            <div>
                <label for="team" />
                <input
                    type="text"
                    name="team"
                    placeholder="Enter Team Name"
                    bind:value={name} />
                <button
                    on:click={async () => {
                        await createTeam(name);
                        all = Promise.all([author, fetchTeams()]);
                        console.log("team created");
                    }}>Create Team</button>
            </div>
        </section>
    {:catch error}
        {error}
        <p>
            Public profile not found
            <a href="/profile/create" use:link>Create Public Profile</a>
        </p>
    {/await}
</section>
Enter fullscreen mode Exit fullscreen mode

The above markup does the following.

  • Displays a list of teams that the user is a part of.
  • Defines a button to delete a team.
  • Defines a button to create new teams.

Next, let's create a page to display the details of each team as defined by the <a> tag in the markup above.

Create a page to display details of a particular team

Create a new file src/routes/Team.svelte.
Under the <script> tag add the following:

<script>
    import { link } from "svelte-spa-router";
    import Loading from "../lib/Loading.svelte";
    import { api } from "../appwrite";
    import { state } from "../store";

    export let params = {};

    let name = "",
        email = "";

    const fetchTeam = () => api.getTeam(params.id);
    const fetchMemberships = () =>
        api.getMemberships(params.id).then(r => r.memberships);
    const createMembership = (email, name) =>
        api.createMembership(
            params.id,
            email,
            ["member"],
            `${window.origin}/#/acceptMembership`,
            name
        );
    const deleteMembership = async (teamId, membershipId) => {
        try {
            await api.deleteMembership(teamId, membershipId);
            all = Promise.all([fetchTeam(), fetchMemberships()]);
        } catch (error) {
            alert(error.message);
        }
    };

    let all = Promise.all([fetchTeam(), fetchMemberships()]);
</script>
Enter fullscreen mode Exit fullscreen mode

Let's add some markup to define the layout:

<section>
    {#await all}
        <Loading />
    {:then [team, memberships]}
        <section>
            <div class="header">
                <h1>{team.name}</h1>
                <button
                    on:click={async () => {
                        api.deleteTeam(params.id).then(() => {
                            window.history.go(-1);
                        });
                    }}>โŒ Delete Team</button>
            </div>
            <div>
                <label for="email" />
                <input
                    type="text"
                    name="email"
                    placeholder="Enter Email Address"
                    bind:value={email} />
                <label for="name" />
                <input
                    type="text"
                    name="name"
                    placeholder="Enter Name"
                    bind:value={name} />
                <button
                    on:click={async () => {
                        await createMembership(email, name);
                        all = Promise.all([fetchTeam(), fetchMemberships()]);
                        console.log("membership created");
                    }}>โž• Add Member</button>
            </div>
            <h3>Members</h3>
            <ul>
                {#each memberships as member}
                    <li>
                        <div>
                            <div>
                                <p>Name : {member.name}</p>
                                {#if member.userId != $state.user.$id}
                                <button on:click={() => deleteMembership(params.id, member.$id)}
                                    >โŒ Delete Member</button>
                                {/if}
                            </div>

                            <p>Email: {member.email}</p>
                            <p>
                                Invited on : {new Date(member.invited * 1000)}
                            </p>
                            <p>Joined on : {new Date(member.joined * 1000)}</p>
                            <p>Confirmed : {member.confirm}</p>
                            <p>Roles : {member.roles}</p>
                        </div>
                    </li>
                {/each}
            </ul>
        </section>
    {:catch error}
        {error}
        <p>
            Team not found
            <a href="/" use:link>Go Home</a>
        </p>
    {/await}
</section>
Enter fullscreen mode Exit fullscreen mode

We will be ignoring the styling here. For more details about the styling you can take a look at the project's repo.

The above markup does a couple of things:

  • Displays a list of members in a particular team.
  • Allow the user to add new members to the team
  • Allow the user to delete members from the team.
  • Allow the user to delete the team.

Create a page to accept team membership

When we click the Add Member button, an email is sent to the invitee with an invite link. The link should redirect the invitee back to your app, where you need to call the Update Team Membership Status method to confirm the membership. In our case, the link would take the user to https://<your domain>/#/acceptMembership. For users who already have an account in your app, it simply adds them to the team. For new users, it creates a new account for them in addition to adding them to the team.

Create a new file src/routes/AcceptMembership.svelte and add the following code in the <script> section:

<script>
    import { api } from "../appwrite";
    let urlSearchParams = new URLSearchParams(window.location.search);
    let inviteId = urlSearchParams.get("inviteId");
    let secret = urlSearchParams.get("secret");
    let teamId = urlSearchParams.get("teamId");
    let userId = urlSearchParams.get("userId");
    api.updateMembership(teamId, inviteId, userId, secret).then(() => {
        window.location = "/"
    });
</script> 
Enter fullscreen mode Exit fullscreen mode

Just like that, you can now create and manage teams in your application! Kudos for making it this far.

Credits

We hope you liked this post. You can follow #30DaysOfAppwrite on Social Media to keep up with all of our posts. The complete event timeline can be found here

Feel free to reach out to us on Discord if you would like to learn more about Appwrite, Aliens or Unicorns ๐Ÿฆ„. Stay tuned for tomorrow's article! Until then ๐Ÿ‘‹

Top comments (1)

Collapse
 
sandorturanszky profile image
Sandor | tutorialhell.dev

For new users, it creates a new account for them in addition to adding them to the team.

What if I want a new user I invited to authenticate with a social account? It's unclear what is meant under "it creates a new account". Is it going to be an email/password account?