Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 45 additions & 2 deletions app/src/app.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useAppStore, useRouteMemoryStore, useWebrtcStore } from '@/stores';
import { useAppStore, useRouteMemoryStore, useUiStore, useWebrtcStore } from '@/stores';
import { getAd4mConnect, isEmbedded } from '@coasys/ad4m-connect';
import { createPinia, storeToRefs } from 'pinia';
import { restoreNeighbourhoodPrefix } from '@/utils/routeUtils';
import { createPinia } from 'pinia';
import { createPersistedState } from 'pinia-plugin-persistedstate';
import { createApp, h } from 'vue';
import { version } from '../package.json';
Expand Down Expand Up @@ -38,6 +39,41 @@ const vueApp = createApp({ render: () => h(App) })
const appStore = useAppStore(pinia);
const routeMemoryStore = useRouteMemoryStore(pinia);

// Pending perspective navigation from WE, queued if received before initialization completes
let pendingPerspectiveNavigation: string | null = null;

function handlePerspectiveNavigation(communityId: string): void {
const key = restoreNeighbourhoodPrefix(communityId);
const privateKey = `private://${communityId}`;
const community = appStore.myCommunities[key] ?? appStore.myCommunities[privateKey];

if (community) {
const lastRoute = routeMemoryStore.getLastCommunityRoute(communityId);
router.push(lastRoute ? lastRoute.path : { name: 'community', params: { communityId } });
} else {
// Perspective exists but has no Flux community — offer to initialise one
router.push({ name: 'init-community', params: { communityId } });
}
}

// When embedded in WE: hide Flux sidebar and listen for perspective navigation messages
if (isEmbedded()) {
const uiStore = useUiStore(pinia);
uiStore.setAppSidebarOpen(false);

window.addEventListener('message', (event: MessageEvent) => {
if (event.data?.type !== 'NAVIGATE_PERSPECTIVE') return;
const communityId = event.data.communityId as string;
if (!communityId) return;

if (!appStore.initialized) {
pendingPerspectiveNavigation = communityId;
} else {
handlePerspectiveNavigation(communityId);
}
});
}

// Tracks the route the user was on when credits ran out, so we can return them after topping up
let savedPreCreditRoute: typeof routeMemoryStore.currentRoute | null = null;

Expand Down Expand Up @@ -102,6 +138,13 @@ vueApp.mount('#app');
const hasFluxAccount = appStore.me.perspective?.links.some((e) => e.data.source.startsWith('flux://'));
if (!hasFluxAccount) return;

// If WE sent a NAVIGATE_PERSPECTIVE before init completed, handle it now and we're done.
if (isEmbedded() && pendingPerspectiveNavigation) {
handlePerspectiveNavigation(pendingPerspectiveNavigation);
pendingPerspectiveNavigation = null;
return;
}

// Determine which params to use for navigation (prioritize current params)
let params = null;
if (currentParams.communityId) params = currentParams;
Expand Down
6 changes: 6 additions & 0 deletions app/src/router/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,12 @@ const routes: Array<RouteRecordRaw> = [
name: 'join-community',
component: () => import('@/views/JoinCommunityView.vue'),
},
{
path: 'init-community/:communityId',
props: true,
name: 'init-community',
component: () => import('@/views/main/InitCommunityView.vue'),
},
],
},
];
Expand Down
104 changes: 104 additions & 0 deletions app/src/views/main/InitCommunityView.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
<template>
<div class="init-community">
<div class="init-community__card">
<template v-if="perspective">
<j-avatar
:initials="perspective.name.charAt(0).toUpperCase()"
size="xl"
/>

<j-flex direction="column" a="center" gap="200">
<j-text variant="heading">{{ perspective.name }}</j-text>
<j-text variant="body" color="color-text-muted" style="text-align: center">
This space doesn't have a Flux community yet. Initialize one to start using channels,
conversations, and other Flux features here.
</j-text>
</j-flex>

<j-button
size="lg"
variant="primary"
:loading="initializing"
:disabled="initializing"
@click="initCommunity"
>
<j-icon name="plus" slot="start" />
Initialize Flux Community
</j-button>
</template>

<template v-else>
<j-icon name="exclamation-triangle" size="xl" color="danger-500" />
<j-flex direction="column" a="center" gap="200">
<j-text variant="heading">Space not accessible</j-text>
<j-text variant="body" color="color-text-muted" style="text-align: center">
This space is not available in Flux.
</j-text>
</j-flex>
</template>
</div>
</div>
</template>

<script setup lang="ts">
import { useAppStore } from '@/stores';
import { stripNeighbourhoodPrefix, restoreNeighbourhoodPrefix } from '@/utils/routeUtils';
import { createCommunity } from '@coasys/flux-api';
import { PerspectiveProxy } from '@coasys/ad4m';
import { computed, ref } from 'vue';
import { useRouter } from 'vue-router';

const props = defineProps<{ communityId: string }>();

const appStore = useAppStore();
const router = useRouter();

const initializing = ref(false);

// Find the perspective by communityId (which is either a stripped neighbourhood URL or a raw UUID)
const perspective = computed((): PerspectiveProxy | undefined => {
return appStore.myPerspectives.find((p) => {
if (p.sharedUrl) return stripNeighbourhoodPrefix(p.sharedUrl) === props.communityId;
return p.uuid === props.communityId;
});
});

async function initCommunity() {
if (!perspective.value || initializing.value) return;
initializing.value = true;
try {
await createCommunity({
perspectiveUuid: perspective.value.uuid,
name: perspective.value.name,
client: appStore.ad4mClient,
});
await appStore.getMyCommunities();
router.push({ name: 'community', params: { communityId: props.communityId } });
} catch (error) {
console.error('Failed to initialize Flux community:', error);
appStore.showDangerToast({ message: 'Failed to initialize Flux community.' });
} finally {
initializing.value = false;
}
}
</script>

<style scoped>
.init-community {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}

.init-community__card {
display: flex;
flex-direction: column;
align-items: center;
gap: var(--j-space-600);
max-width: 400px;
padding: var(--j-space-800);
text-align: center;
}
</style>
Loading