Skip to content

Commit

Permalink
Merge pull request #246 from codex-team/join-route-handle
Browse files Browse the repository at this point in the history
feat(pages): Join route handle
  • Loading branch information
e11sy authored Jul 4, 2024
2 parents c3d4d06 + 5c36849 commit d901e8a
Show file tree
Hide file tree
Showing 10 changed files with 246 additions and 2 deletions.
11 changes: 10 additions & 1 deletion src/application/i18n/messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,14 @@
"500": "Unknown error happened",
"default": "Something went wrong"
},
"join": {
"title": "Joining a note team...",
"messages": {
"linkExpired": "This join link has been expired",
"validationError": "Wrong invitation hash specified",
"unauthorized": "You must be authenticated to access this resource"
}
},
"marketplace": {
"title": "Marketplace",
"listOfTools": "Tools",
Expand Down Expand Up @@ -146,6 +154,7 @@
"noteSettings": "Note settings",
"marketplace": "Marketplace",
"addTool": "Add tool",
"notFound": "Not found"
"notFound": "Not found",
"joinTeam": "Join"
}
}
25 changes: 25 additions & 0 deletions src/application/router/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import Landing from '@/presentation/pages/Landing.vue';
import Settings from '@/presentation/pages/Settings.vue';
import NoteSettings from '@/presentation/pages/NoteSettings.vue';
import ErrorPage from '@/presentation/pages/Error.vue';
import JoinPage from '@/presentation/pages/Join.vue';
import type { RouteRecordRaw } from 'vue-router';
import AddTool from '@/presentation/pages/marketplace/AddTool.vue';
import MarketplacePage from '@/presentation/pages/marketplace/MarketplacePage.vue';
Expand Down Expand Up @@ -114,6 +115,30 @@ const routes: RouteRecordRaw[] = [
},
}],
},
{
name: 'join',
path: '/join/:hash',
component: JoinPage,
props: route => ({
invitationHash: String(route.params.hash),
}),
meta: {
pageTitleI18n: 'pages.joinTeam',
discardTabOnLeave: true,
},
},
{
name: 'join',
path: '/join/:hash',
component: JoinPage,
props: route => ({
invitationHash: String(route.params.hash),
}),
meta: {
pageTitleI18n: 'pages.joinTeam',
discardTabOnLeave: true,
},
},

/**
* 404 page
Expand Down
27 changes: 27 additions & 0 deletions src/application/services/useTeam.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import type { InvitationHash } from '@/domain/entities/NoteSettings';
import type { TeamMember } from '@/domain/entities/Team';
import { teamService } from '@/domain/index';

interface useTeamComposableState {
/**
* Function for adding user to note's team by invitation hash
* @param hash - invitation hash of the certain note
* @returns Team member or null
*/
joinNoteTeamByHash: (hash: InvitationHash) => Promise<TeamMember | null>;
}

export default function useTeam(): useTeamComposableState {
/**
* Function for adding user to note's team by invitation hash
* @param hash - invitation hash of the certain note
* @returns Team member or null
*/
async function joinNoteTeamByHash(hash: InvitationHash): Promise<TeamMember | null> {
return await teamService.joinNoteTeamByHash(hash);
}

return {
joinNoteTeamByHash,
};
}
6 changes: 6 additions & 0 deletions src/domain/entities/Team.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { NoteId } from './Note.js';
import type { User } from './User.js';

export enum MemberRole {
Expand All @@ -22,6 +23,11 @@ export interface TeamMember {
*/
id: number;

/**
* User is in team of the note with such an id
*/
noteId: NoteId;

/**
* Team member user
*/
Expand Down
5 changes: 4 additions & 1 deletion src/domain/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import EventBus from './event-bus';
import NoteListService from './noteList.service';
import EditorToolsService from '@/domain/editorTools.service';
import WorkspaceService from './workspace.service';
import TeamService from './team.service';
/**
* Get API url from environment
*/
Expand All @@ -34,6 +35,7 @@ const noteSettingsService = new NoteSettingsService(repositories.noteSettings, r
const authService = new AuthService(eventBus, repositories.auth);
const userService = new UserService(eventBus, repositories.user);
const marketplaceService = new MarketplaceService(repositories.marketplace);
const teamService = new TeamService(repositories.team);

/**
* App State — is a read-only combination of app Stores.
Expand All @@ -57,5 +59,6 @@ export {
authService,
userService,
marketplaceService,
workspaceService
workspaceService,
teamService
};
15 changes: 15 additions & 0 deletions src/domain/team.repository.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import type { InvitationHash } from '@/domain/entities/NoteSettings';
import type { TeamMember } from '@/domain/entities/Team';

/**
* Repository interface describes the methods that required by domain for its business logic implementation
*/
export default interface TeamRepositoryInterface {
/**
* Function for adding user to note's team by invitation hash
* @param hash - invitation hash of the certain note
* @returns Team member or null
*/
joinNoteTeamByHash: (hash: InvitationHash) => Promise<TeamMember | null>;

}
26 changes: 26 additions & 0 deletions src/domain/team.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import type TeamRepository from '@/domain/team.repository.interface';
import type { InvitationHash } from './entities/NoteSettings';
import type { TeamMember } from './entities/Team';

/**
* Team service
*/
export default class TeamService {
/**
* Team repository
*/
private readonly teamRepository: TeamRepository;

constructor(teamRepository: TeamRepository) {
this.teamRepository = teamRepository;
}

/**
* Function for adding user to note's team by invitation hash
* @param hash - invitation hash of the certain note
* @returns Team member or null
*/
public async joinNoteTeamByHash(hash: InvitationHash): Promise<TeamMember | null> {
return this.teamRepository.joinNoteTeamByHash(hash);
}
}
8 changes: 8 additions & 0 deletions src/infrastructure/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { EditorToolsStore } from '@/infrastructure/storage/editorTools.ts';
import EditorToolsRepository from '@/infrastructure/editorTools.repository';
import EditorToolsTransport from '@/infrastructure/transport/editorTools.transport';
import NoteAttachmentUploaderRepository from './noteAttachmentUploader.repository';
import TeamRepository from '@/infrastructure/team.repository';

/**
* Repositories
Expand Down Expand Up @@ -60,6 +61,11 @@ export interface Repositories {
* Working with all pages user is currently using
*/
workspace: WorkspaceRepository;

/**
* Working with teams
*/
team: TeamRepository;
}

/**
Expand Down Expand Up @@ -126,6 +132,7 @@ export function init(noteApiUrl: string, eventBus: EventBus): Repositories {
const editorToolsRepository = new EditorToolsRepository(editorToolsStore, editorToolsTransport);
const noteAttachmentUploaderRepository = new NoteAttachmentUploaderRepository(notesApiTransport);
const workspaceRepository = new WorkspaceRepository(openedPagesStore);
const teamRepository = new TeamRepository(notesApiTransport);

return {
note: noteRepository,
Expand All @@ -136,5 +143,6 @@ export function init(noteApiUrl: string, eventBus: EventBus): Repositories {
editorTools: editorToolsRepository,
noteAttachmentUploader: noteAttachmentUploaderRepository,
workspace: workspaceRepository,
team: teamRepository,
};
}
34 changes: 34 additions & 0 deletions src/infrastructure/team.repository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import type TeamRepositoryInterface from '@/domain/team.repository.interface';
import type NotesApiTransport from '@/infrastructure/transport/notes-api';
import type { InvitationHash } from '@/domain/entities/NoteSettings';
import type { TeamMember } from '@/domain/entities/Team';

/**
* Team repository
*/
export default class TeamRepository implements TeamRepositoryInterface {
/**
* Transport instance
*/
private readonly transport: NotesApiTransport;
/**
* Note repository constructor
* @param notesApiTransport - notes api transport instance
*/
constructor(notesApiTransport: NotesApiTransport) {
this.transport = notesApiTransport;
}

/**
* Function for adding user to note's team by invitation hash
* @param hash - invitation hash of the certain note
* @returns Team member or null
*/
public async joinNoteTeamByHash(hash: InvitationHash): Promise<TeamMember | null> {
let res: TeamMember | null = null;

res = await this.transport.post<TeamMember | null>({ endpoint: '/join/' + hash });

return res;
}
}
91 changes: 91 additions & 0 deletions src/presentation/pages/Join.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
<template>
<div :class="$style['message']">
{{ message }}
</div>
</template>

<script setup lang="ts">
import { useRouter } from 'vue-router';
import { ref, watch } from 'vue';
import useTeam from '@/application/services/useTeam';
import { useAppState } from '@/application/services/useAppState';
import useAuth from '@/application/services/useAuth';
import type { TeamMember } from '@/domain/entities/Team';
import { InvitationHash } from '@/domain/entities/NoteSettings';
import { useI18n } from 'vue-i18n';
const { user } = useAppState();
const { t } = useI18n();
const { showGoogleAuthPopup } = useAuth();
const { joinNoteTeamByHash } = useTeam();
const router = useRouter();
const props = defineProps<{
invitationHash: InvitationHash;
}>();
/**
* Message to be displayed as a heading of join page
*/
const message = ref(t('join.title'));
const teamMember = ref<TeamMember | null>(null);
async function handleJoin(): Promise<void> {
try {
teamMember.value = await joinNoteTeamByHash(props.invitationHash);
} catch (error) {
if (error instanceof Error) {
/**
* Handle errors which are related to wrong invitation hash specified
*/
if (error.message === 'FST_ERR_VALIDATION') {
message.value = t('join.messages.validationError');
}
/**
* Handle error related to expired invitation link
*/
if (error.message === 'Wrong invitation') {
message.value = t('join.messages.linkExpired');
}
/**
* Handle errors related to unauthorized state
*/
if (error.message === 'You must be authenticated to access this resource') {
message.value = t('join.messages.unauthorized');
showGoogleAuthPopup();
}
}
}
/**
* Check if we got id of the note to redirect
*/
if (teamMember.value?.noteId) {
router.push(`/note/${teamMember.value?.noteId}`);
/**
* @todo implement success alert
*/
}
}
/**
* Watching authorization of the user
*/
watch(user, async () => {
await handleJoin();
}, { immediate: true });
</script>

<style lang="postcss" module>
.message {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
</style>

0 comments on commit d901e8a

Please sign in to comment.