initial commit

This commit is contained in:
2022-09-23 08:46:47 -06:00
commit 1921efce37
191 changed files with 22614 additions and 0 deletions

View File

@ -0,0 +1,66 @@
<script setup>
import { useForm } from '@inertiajs/inertia-vue3';
import FormSection from '@/Components/FormSection.vue';
import InputError from '@/Components/InputError.vue';
import InputLabel from '@/Components/InputLabel.vue';
import PrimaryButton from '@/Components/PrimaryButton.vue';
import TextInput from '@/Components/TextInput.vue';
const form = useForm({
name: '',
});
const createTeam = () => {
form.post(route('teams.store'), {
errorBag: 'createTeam',
preserveScroll: true,
});
};
</script>
<template>
<FormSection @submitted="createTeam">
<template #title>
Team Details
</template>
<template #description>
Create a new team to collaborate with others on projects.
</template>
<template #form>
<div class="col-span-6">
<InputLabel value="Team Owner" />
<div class="flex items-center mt-2">
<img class="object-cover w-12 h-12 rounded-full" :src="$page.props.user.profile_photo_url" :alt="$page.props.user.name">
<div class="ml-4 leading-tight">
<div>{{ $page.props.user.name }}</div>
<div class="text-sm text-gray-700">
{{ $page.props.user.email }}
</div>
</div>
</div>
</div>
<div class="col-span-6 sm:col-span-4">
<InputLabel for="name" value="Team Name" />
<TextInput
id="name"
v-model="form.name"
type="text"
class="block w-full mt-1"
autofocus
/>
<InputError :message="form.errors.name" class="mt-2" />
</div>
</template>
<template #actions>
<PrimaryButton :class="{ 'opacity-25': form.processing }" :disabled="form.processing">
Create
</PrimaryButton>
</template>
</FormSection>
</template>

View File

@ -0,0 +1,75 @@
<script setup>
import { ref } from 'vue';
import { useForm } from '@inertiajs/inertia-vue3';
import ActionSection from '@/Components/ActionSection.vue';
import ConfirmationModal from '@/Components/ConfirmationModal.vue';
import DangerButton from '@/Components/DangerButton.vue';
import SecondaryButton from '@/Components/SecondaryButton.vue';
const props = defineProps({
team: Object,
});
const confirmingTeamDeletion = ref(false);
const form = useForm();
const confirmTeamDeletion = () => {
confirmingTeamDeletion.value = true;
};
const deleteTeam = () => {
form.delete(route('teams.destroy', props.team), {
errorBag: 'deleteTeam',
});
};
</script>
<template>
<ActionSection>
<template #title>
Delete Team
</template>
<template #description>
Permanently delete this team.
</template>
<template #content>
<div class="max-w-xl text-sm text-gray-600">
Once a team is deleted, all of its resources and data will be permanently deleted. Before deleting this team, please download any data or information regarding this team that you wish to retain.
</div>
<div class="mt-5">
<DangerButton @click="confirmTeamDeletion">
Delete Team
</DangerButton>
</div>
<!-- Delete Team Confirmation Modal -->
<ConfirmationModal :show="confirmingTeamDeletion" @close="confirmingTeamDeletion = false">
<template #title>
Delete Team
</template>
<template #content>
Are you sure you want to delete this team? Once a team is deleted, all of its resources and data will be permanently deleted.
</template>
<template #footer>
<SecondaryButton @click="confirmingTeamDeletion = false">
Cancel
</SecondaryButton>
<DangerButton
class="ml-3"
:class="{ 'opacity-25': form.processing }"
:disabled="form.processing"
@click="deleteTeam"
>
Delete Team
</DangerButton>
</template>
</ConfirmationModal>
</template>
</ActionSection>
</template>

View File

@ -0,0 +1,396 @@
<script setup>
import { ref } from 'vue';
import { Inertia } from '@inertiajs/inertia';
import { useForm, usePage } from '@inertiajs/inertia-vue3';
import ActionMessage from '@/Components/ActionMessage.vue';
import ActionSection from '@/Components/ActionSection.vue';
import ConfirmationModal from '@/Components/ConfirmationModal.vue';
import DangerButton from '@/Components/DangerButton.vue';
import DialogModal from '@/Components/DialogModal.vue';
import FormSection from '@/Components/FormSection.vue';
import InputError from '@/Components/InputError.vue';
import InputLabel from '@/Components/InputLabel.vue';
import PrimaryButton from '@/Components/PrimaryButton.vue';
import SecondaryButton from '@/Components/SecondaryButton.vue';
import SectionBorder from '@/Components/SectionBorder.vue';
import TextInput from '@/Components/TextInput.vue';
const props = defineProps({
team: Object,
availableRoles: Array,
userPermissions: Object,
});
const addTeamMemberForm = useForm({
email: '',
role: null,
});
const updateRoleForm = useForm({
role: null,
});
const leaveTeamForm = useForm();
const removeTeamMemberForm = useForm();
const currentlyManagingRole = ref(false);
const managingRoleFor = ref(null);
const confirmingLeavingTeam = ref(false);
const teamMemberBeingRemoved = ref(null);
const addTeamMember = () => {
addTeamMemberForm.post(route('team-members.store', props.team), {
errorBag: 'addTeamMember',
preserveScroll: true,
onSuccess: () => addTeamMemberForm.reset(),
});
};
const cancelTeamInvitation = (invitation) => {
Inertia.delete(route('team-invitations.destroy', invitation), {
preserveScroll: true,
});
};
const manageRole = (teamMember) => {
managingRoleFor.value = teamMember;
updateRoleForm.role = teamMember.membership.role;
currentlyManagingRole.value = true;
};
const updateRole = () => {
updateRoleForm.put(route('team-members.update', [props.team, managingRoleFor.value]), {
preserveScroll: true,
onSuccess: () => currentlyManagingRole.value = false,
});
};
const confirmLeavingTeam = () => {
confirmingLeavingTeam.value = true;
};
const leaveTeam = () => {
leaveTeamForm.delete(route('team-members.destroy', [props.team, usePage().props.value.user]));
};
const confirmTeamMemberRemoval = (teamMember) => {
teamMemberBeingRemoved.value = teamMember;
};
const removeTeamMember = () => {
removeTeamMemberForm.delete(route('team-members.destroy', [props.team, teamMemberBeingRemoved.value]), {
errorBag: 'removeTeamMember',
preserveScroll: true,
preserveState: true,
onSuccess: () => teamMemberBeingRemoved.value = null,
});
};
const displayableRole = (role) => {
return props.availableRoles.find(r => r.key === role).name;
};
</script>
<template>
<div>
<div v-if="userPermissions.canAddTeamMembers">
<SectionBorder />
<!-- Add Team Member -->
<FormSection @submitted="addTeamMember">
<template #title>
Add Team Member
</template>
<template #description>
Add a new team member to your team, allowing them to collaborate with you.
</template>
<template #form>
<div class="col-span-6">
<div class="max-w-xl text-sm text-gray-600">
Please provide the email address of the person you would like to add to this team.
</div>
</div>
<!-- Member Email -->
<div class="col-span-6 sm:col-span-4">
<InputLabel for="email" value="Email" />
<TextInput
id="email"
v-model="addTeamMemberForm.email"
type="email"
class="mt-1 block w-full"
/>
<InputError :message="addTeamMemberForm.errors.email" class="mt-2" />
</div>
<!-- Role -->
<div v-if="availableRoles.length > 0" class="col-span-6 lg:col-span-4">
<InputLabel for="roles" value="Role" />
<InputError :message="addTeamMemberForm.errors.role" class="mt-2" />
<div class="relative z-0 mt-1 border border-gray-200 rounded-lg cursor-pointer">
<button
v-for="(role, i) in availableRoles"
:key="role.key"
type="button"
class="relative px-4 py-3 inline-flex w-full rounded-lg focus:z-10 focus:outline-none focus:border-blue-300 focus:ring focus:ring-blue-200"
:class="{'border-t border-gray-200 rounded-t-none': i > 0, 'rounded-b-none': i != Object.keys(availableRoles).length - 1}"
@click="addTeamMemberForm.role = role.key"
>
<div :class="{'opacity-50': addTeamMemberForm.role && addTeamMemberForm.role != role.key}">
<!-- Role Name -->
<div class="flex items-center">
<div class="text-sm text-gray-600" :class="{'font-semibold': addTeamMemberForm.role == role.key}">
{{ role.name }}
</div>
<svg
v-if="addTeamMemberForm.role == role.key"
class="ml-2 h-5 w-5 text-green-400"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
stroke="currentColor"
viewBox="0 0 24 24"
><path d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
</div>
<!-- Role Description -->
<div class="mt-2 text-xs text-gray-600 text-left">
{{ role.description }}
</div>
</div>
</button>
</div>
</div>
</template>
<template #actions>
<ActionMessage :on="addTeamMemberForm.recentlySuccessful" class="mr-3">
Added.
</ActionMessage>
<PrimaryButton :class="{ 'opacity-25': addTeamMemberForm.processing }" :disabled="addTeamMemberForm.processing">
Add
</PrimaryButton>
</template>
</FormSection>
</div>
<div v-if="team.team_invitations.length > 0 && userPermissions.canAddTeamMembers">
<SectionBorder />
<!-- Team Member Invitations -->
<ActionSection class="mt-10 sm:mt-0">
<template #title>
Pending Team Invitations
</template>
<template #description>
These people have been invited to your team and have been sent an invitation email. They may join the team by accepting the email invitation.
</template>
<!-- Pending Team Member Invitation List -->
<template #content>
<div class="space-y-6">
<div v-for="invitation in team.team_invitations" :key="invitation.id" class="flex items-center justify-between">
<div class="text-gray-600">
{{ invitation.email }}
</div>
<div class="flex items-center">
<!-- Cancel Team Invitation -->
<button
v-if="userPermissions.canRemoveTeamMembers"
class="cursor-pointer ml-6 text-sm text-red-500 focus:outline-none"
@click="cancelTeamInvitation(invitation)"
>
Cancel
</button>
</div>
</div>
</div>
</template>
</ActionSection>
</div>
<div v-if="team.users.length > 0">
<SectionBorder />
<!-- Manage Team Members -->
<ActionSection class="mt-10 sm:mt-0">
<template #title>
Team Members
</template>
<template #description>
All of the people that are part of this team.
</template>
<!-- Team Member List -->
<template #content>
<div class="space-y-6">
<div v-for="user in team.users" :key="user.id" class="flex items-center justify-between">
<div class="flex items-center">
<img class="w-8 h-8 rounded-full" :src="user.profile_photo_url" :alt="user.name">
<div class="ml-4">
{{ user.name }}
</div>
</div>
<div class="flex items-center">
<!-- Manage Team Member Role -->
<button
v-if="userPermissions.canAddTeamMembers && availableRoles.length"
class="ml-2 text-sm text-gray-400 underline"
@click="manageRole(user)"
>
{{ displayableRole(user.membership.role) }}
</button>
<div v-else-if="availableRoles.length" class="ml-2 text-sm text-gray-400">
{{ displayableRole(user.membership.role) }}
</div>
<!-- Leave Team -->
<button
v-if="$page.props.user.id === user.id"
class="cursor-pointer ml-6 text-sm text-red-500"
@click="confirmLeavingTeam"
>
Leave
</button>
<!-- Remove Team Member -->
<button
v-else-if="userPermissions.canRemoveTeamMembers"
class="cursor-pointer ml-6 text-sm text-red-500"
@click="confirmTeamMemberRemoval(user)"
>
Remove
</button>
</div>
</div>
</div>
</template>
</ActionSection>
</div>
<!-- Role Management Modal -->
<DialogModal :show="currentlyManagingRole" @close="currentlyManagingRole = false">
<template #title>
Manage Role
</template>
<template #content>
<div v-if="managingRoleFor">
<div class="relative z-0 mt-1 border border-gray-200 rounded-lg cursor-pointer">
<button
v-for="(role, i) in availableRoles"
:key="role.key"
type="button"
class="relative px-4 py-3 inline-flex w-full rounded-lg focus:z-10 focus:outline-none focus:border-blue-300 focus:ring focus:ring-blue-200"
:class="{'border-t border-gray-200 rounded-t-none': i > 0, 'rounded-b-none': i !== Object.keys(availableRoles).length - 1}"
@click="updateRoleForm.role = role.key"
>
<div :class="{'opacity-50': updateRoleForm.role && updateRoleForm.role !== role.key}">
<!-- Role Name -->
<div class="flex items-center">
<div class="text-sm text-gray-600" :class="{'font-semibold': updateRoleForm.role === role.key}">
{{ role.name }}
</div>
<svg
v-if="updateRoleForm.role === role.key"
class="ml-2 h-5 w-5 text-green-400"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
stroke="currentColor"
viewBox="0 0 24 24"
><path d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
</div>
<!-- Role Description -->
<div class="mt-2 text-xs text-gray-600">
{{ role.description }}
</div>
</div>
</button>
</div>
</div>
</template>
<template #footer>
<SecondaryButton @click="currentlyManagingRole = false">
Cancel
</SecondaryButton>
<PrimaryButton
class="ml-3"
:class="{ 'opacity-25': updateRoleForm.processing }"
:disabled="updateRoleForm.processing"
@click="updateRole"
>
Save
</PrimaryButton>
</template>
</DialogModal>
<!-- Leave Team Confirmation Modal -->
<ConfirmationModal :show="confirmingLeavingTeam" @close="confirmingLeavingTeam = false">
<template #title>
Leave Team
</template>
<template #content>
Are you sure you would like to leave this team?
</template>
<template #footer>
<SecondaryButton @click="confirmingLeavingTeam = false">
Cancel
</SecondaryButton>
<DangerButton
class="ml-3"
:class="{ 'opacity-25': leaveTeamForm.processing }"
:disabled="leaveTeamForm.processing"
@click="leaveTeam"
>
Leave
</DangerButton>
</template>
</ConfirmationModal>
<!-- Remove Team Member Confirmation Modal -->
<ConfirmationModal :show="teamMemberBeingRemoved" @close="teamMemberBeingRemoved = null">
<template #title>
Remove Team Member
</template>
<template #content>
Are you sure you would like to remove this person from the team?
</template>
<template #footer>
<SecondaryButton @click="teamMemberBeingRemoved = null">
Cancel
</SecondaryButton>
<DangerButton
class="ml-3"
:class="{ 'opacity-25': removeTeamMemberForm.processing }"
:disabled="removeTeamMemberForm.processing"
@click="removeTeamMember"
>
Remove
</DangerButton>
</template>
</ConfirmationModal>
</div>
</template>

View File

@ -0,0 +1,80 @@
<script setup>
import { useForm } from '@inertiajs/inertia-vue3';
import ActionMessage from '@/Components/ActionMessage.vue';
import FormSection from '@/Components/FormSection.vue';
import InputError from '@/Components/InputError.vue';
import InputLabel from '@/Components/InputLabel.vue';
import PrimaryButton from '@/Components/PrimaryButton.vue';
import TextInput from '@/Components/TextInput.vue';
const props = defineProps({
team: Object,
permissions: Object,
});
const form = useForm({
name: props.team.name,
});
const updateTeamName = () => {
form.put(route('teams.update', props.team), {
errorBag: 'updateTeamName',
preserveScroll: true,
});
};
</script>
<template>
<FormSection @submitted="updateTeamName">
<template #title>
Team Name
</template>
<template #description>
The team's name and owner information.
</template>
<template #form>
<!-- Team Owner Information -->
<div class="col-span-6">
<InputLabel value="Team Owner" />
<div class="flex items-center mt-2">
<img class="w-12 h-12 rounded-full object-cover" :src="team.owner.profile_photo_url" :alt="team.owner.name">
<div class="ml-4 leading-tight">
<div>{{ team.owner.name }}</div>
<div class="text-gray-700 text-sm">
{{ team.owner.email }}
</div>
</div>
</div>
</div>
<!-- Team Name -->
<div class="col-span-6 sm:col-span-4">
<InputLabel for="name" value="Team Name" />
<TextInput
id="name"
v-model="form.name"
type="text"
class="mt-1 block w-full"
:disabled="! permissions.canUpdateTeam"
/>
<InputError :message="form.errors.name" class="mt-2" />
</div>
</template>
<template v-if="permissions.canUpdateTeam" #actions>
<ActionMessage :on="form.recentlySuccessful" class="mr-3">
Saved.
</ActionMessage>
<PrimaryButton :class="{ 'opacity-25': form.processing }" :disabled="form.processing">
Save
</PrimaryButton>
</template>
</FormSection>
</template>