Skip to content

Commit

Permalink
Community details update (#266)
Browse files Browse the repository at this point in the history
* Add community name change input

* add game types to info. move info into main page
  • Loading branch information
jongrim authored Mar 11, 2024
1 parent 40f5735 commit eae48f2
Show file tree
Hide file tree
Showing 6 changed files with 366 additions and 155 deletions.
24 changes: 23 additions & 1 deletion src/api/communities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,28 @@ export async function getCommunityMemberCount(id: string) {
.eq("community_id", id);
}

export async function isCommunityNameAvailable({
name,
id,
}: {
name: string;
id: string;
}) {
const { error, status } = await supabase
.from("communities")
.select("id")
.eq("name", name)
.neq("id", id)
.is("deleted_at", null)
.single();
if (error && status === 406) {
return true;
} else if (error) {
log({ error });
}
return false;
}

export async function isShortNameAvailable({
shortName,
id,
Expand Down Expand Up @@ -176,7 +198,7 @@ export async function leaveCommunity(communityId: string, userId = "") {
headers: {
token: session.access_token,
},
},
}
)
.catch((error) => {
log({ error });
Expand Down
56 changes: 6 additions & 50 deletions src/pages/Community/CommunityInfo.vue
Original file line number Diff line number Diff line change
@@ -1,33 +1,15 @@
<template>
<SectionContainer>
<CommunityNameChange />
</SectionContainer>
<SectionContainer>
<CommunityShortName />
</SectionContainer>
<SectionContainer>
<CommunityEmailSettings />
</SectionContainer>
<SectionContainer class="grid sm:grid-cols-2 md:grid-cols-3 gap-2">
<div class="flex justify-between items-center mb-4 col-span-full">
<Heading level="h6" as="h2"> Community Info </Heading>
<GhostButton
aria-label="Edit community info"
@click="editInfoDrawerOpen = true"
>
<PencilSquareIcon class="h-5 w-5 text-slate-700" />
</GhostButton>
</div>
<div
v-for="detail in details"
:key="detail.label"
class="flex items-center space-x-4"
>
<CheckCircleIcon
v-if="detail.value"
class="h-6 w-6"
:class="{ 'text-blue-700': detail.value }"
/>
<MinusCircleIcon v-else class="h-6 w-6 text-slate-700" />
<p class="prose dark:prose-invert">{{ detail.label }}</p>
</div>
<SectionContainer class="grid gap-2">
<EditCommunityInfo />
</SectionContainer>
<SectionContainer>
<CommunityImageLibrary />
Expand All @@ -41,43 +23,17 @@
<SectionContainer>
<AllowPreSeat :community-id="communityStore.community.id" />
</SectionContainer>
<SideDrawer :open="editInfoDrawerOpen" @close="editInfoDrawerOpen = false">
<EditCommunityInfo @close="editInfoDrawerOpen = false" />
</SideDrawer>
</template>
<script setup lang="ts">
import { ref, computed } from "vue";
import {
CheckCircleIcon,
MinusCircleIcon,
PencilSquareIcon,
} from "@heroicons/vue/24/outline";
import Heading from "@/components/Heading.vue";
import GhostButton from "@/components/Buttons/GhostButton.vue";
import SectionContainer from "@/components/SectionContainer.vue";
import CalendarCutoff from "./CalendarCutoff.vue";
import { communityStore } from "./communityStore";
import CommunityImageLibrary from "./CommunityImageLibrary.vue";
import SideDrawer from "@/components/SideDrawer.vue";
import EditCommunityInfo from "./EditCommunityInfo.vue";
import CommunityEmailSettings from "./CommunityEmailSettings.vue";
import CommunityShortName from "./CommunityShortName.vue";
import AllowPreSeat from "./AllowPreSeat.vue";
const editInfoDrawerOpen = ref(false);
const details = computed(() => [
{ value: communityStore.community.description, label: "Description" },
{ value: communityStore.community.how_to_join, label: "How to join" },
{ value: communityStore.community.website, label: "Website" },
{
value: communityStore.community.code_of_conduct_url,
label: "Code of conduct",
},
{ value: communityStore.community.twitter, label: "Twitter" },
{ value: communityStore.community.facebook, label: "Facebook" },
{ value: communityStore.community.discord, label: "Discord" },
{ value: communityStore.community.slack, label: "Slack" },
{ value: communityStore.community.patreon, label: "Patreon" },
]);
import CommunityNameChange from "./CommunityNameChange.vue";
</script>
62 changes: 62 additions & 0 deletions src/pages/Community/CommunityNameChange.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<template>
<form @submit.prevent="handleSave">
<Heading as="h2" level="h6" class="mb-2"
>Change your community name</Heading
>
<p class="mb-6 text-sm text-slate-700">
Community names must be unique. Enter your desired name to check if it is
available.
</p>
<CommunityNameInput
:current-name="communityStore.community.name"
@available="setNextName"
@unavailable="setNextName"
/>
<PrimaryButton
:disabled="!nextName"
:is-loading="saving"
class="mt-6 w-full"
@click="handleSave"
>Save</PrimaryButton
>
</form>
</template>
<script setup lang="ts">
import { ref } from "vue";
import Heading from "@/components/Heading.vue";
import PrimaryButton from "@/components/Buttons/PrimaryButton.vue";
import useToast from "@/components/Toast/useToast";
import { communityStore } from "./communityStore";
import CommunityNameInput from "./CommunityNameInput.vue";
import { updateCommunity } from "@/api/communities";
import { log } from "@/util/logger";
const { showSuccess, showError } = useToast();
const saving = ref(false);
const nextName = ref<string | undefined>();
function setNextName(val?: string) {
nextName.value = val;
}
async function handleSave() {
saving.value = true;
try {
const updated = await updateCommunity({
communityId: communityStore.community.id,
update: { name: nextName.value },
});
communityStore.community.name = updated.name;
showSuccess({ message: "Community name updated" });
nextName.value = "";
} catch (error) {
log({ error });
showError({
message:
"Unable to update community name. Please try again and report the issue.",
});
} finally {
saving.value = false;
}
}
</script>
119 changes: 119 additions & 0 deletions src/pages/Community/CommunityNameInput.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
<template>
<div class="relative">
<FormInput v-model="name" class="w-full" />
<svg
v-if="state === 'searching'"
class="animate-spin absolute top-2 right-3 h-6 w-6 text-brand-500"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle
class="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
stroke-width="4"
></circle>
<path
class="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
></path>
</svg>
<CheckCircleIcon
v-else-if="state === 'available'"
class="absolute top-2 right-3 h-6 w-6 text-green-600"
/>
<XCircleIcon
v-else-if="state === 'unavailable'"
class="absolute top-2 right-3 h-6 w-6 text-red-600"
/>
</div>
<p class="text-sm text-slate-700 mt-2">
<template v-if="state === 'initial'">
This is your current community name
</template>
<template v-else-if="state === 'available'">
<span class="font-semibold">{{ name }}</span> is available!
</template>
<template v-else-if="state === 'unavailable' && name !== ''">
<span class="font-semibold"
>{{ name }} is <span class="font-semibold">not</span> available. Please
select another name.</span
>
</template>
<template v-else-if="state === 'unavailable' && name === ''">
Enter the new name you would like to check
</template>
<template v-else-if="state === 'searching' || state === 'updated'">
Checking...
</template>
</p>
</template>
<script setup lang="ts">
import { ref, watch } from "vue";
import { watchDebounced } from "@vueuse/core";
import { CheckCircleIcon, XCircleIcon } from "@heroicons/vue/20/solid";
import FormInput from "@/components/Forms/FormInput.vue";
import { isCommunityNameAvailable } from "@/api/communities";
import { communityStore } from "./communityStore";
const props = defineProps<{
currentName: string;
}>();
const emit = defineEmits<{
available: [value: string];
unavailable: [];
}>();
const state = ref<
"initial" | "updated" | "searching" | "available" | "unavailable"
>("initial");
const name = ref<string>(props.currentName);
watch(
() => props.currentName,
() => {
state.value = "initial";
name.value = props.currentName;
}
);
watch(name, (val) => {
if (val === props.currentName) {
state.value = "initial";
emit("unavailable");
return;
} else if (val === "") {
state.value = "unavailable";
emit("unavailable");
return;
}
state.value = "updated";
});
watchDebounced(
name,
async (val) => {
if (state.value !== "updated") return;
state.value = "searching";
emit("unavailable");
const isAvailable = await isCommunityNameAvailable({
name: val,
id: communityStore.community.id,
});
if (isAvailable) {
state.value = "available";
emit("available", val);
} else {
state.value = "unavailable";
emit("unavailable");
}
},
{ debounce: 750 }
);
</script>
Loading

0 comments on commit eae48f2

Please sign in to comment.