diff --git a/frontend/src/app/dashboard/board/platforms/platforms.component.html b/frontend/src/app/dashboard/board/platforms/platforms.component.html
index dec4623..d4c4987 100644
--- a/frontend/src/app/dashboard/board/platforms/platforms.component.html
+++ b/frontend/src/app/dashboard/board/platforms/platforms.component.html
@@ -39,4 +39,5 @@
+
diff --git a/frontend/src/app/platforms/mastodon/mastodon-bot-card/mastodon-bot-card.component.html b/frontend/src/app/platforms/mastodon/mastodon-bot-card/mastodon-bot-card.component.html
new file mode 100644
index 0000000..a4ab1a2
--- /dev/null
+++ b/frontend/src/app/platforms/mastodon/mastodon-bot-card/mastodon-bot-card.component.html
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+ mastodon
+
+ info
+
+
+
+
+
+
+
+ @{{ mastodon.username }}
+
+
+
+
+
+
+
+
+ Unfortunately your hood admin has not configured mastodon as platform
+ yet.
+
+
+
+
warning
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/src/app/platforms/mastodon/mastodon-bot-card/mastodon-bot-card.component.scss b/frontend/src/app/platforms/mastodon/mastodon-bot-card/mastodon-bot-card.component.scss
new file mode 100644
index 0000000..e69de29
diff --git a/frontend/src/app/platforms/mastodon/mastodon-bot-card/mastodon-bot-card.component.spec.ts b/frontend/src/app/platforms/mastodon/mastodon-bot-card/mastodon-bot-card.component.spec.ts
new file mode 100644
index 0000000..fa631eb
--- /dev/null
+++ b/frontend/src/app/platforms/mastodon/mastodon-bot-card/mastodon-bot-card.component.spec.ts
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { MastodonBotCardComponent } from './mastodon-bot-card.component';
+
+describe('MastodonBotCardComponent', () => {
+ let component: MastodonBotCardComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ declarations: [ MastodonBotCardComponent ]
+ })
+ .compileComponents();
+
+ fixture = TestBed.createComponent(MastodonBotCardComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/frontend/src/app/platforms/mastodon/mastodon-bot-card/mastodon-bot-card.component.ts b/frontend/src/app/platforms/mastodon/mastodon-bot-card/mastodon-bot-card.component.ts
new file mode 100644
index 0000000..a00bbe0
--- /dev/null
+++ b/frontend/src/app/platforms/mastodon/mastodon-bot-card/mastodon-bot-card.component.ts
@@ -0,0 +1,27 @@
+import { Component, OnInit, Input } from '@angular/core';
+import { MastodonService } from 'src/app/core/api';
+import { MastodonBotInfoDialogComponent } from './mastodon-bot-info-dialog/mastodon-bot-info-dialog.component';
+import { MatDialog } from '@angular/material/dialog';
+
+@Component({
+ selector: 'app-mastodon-bot-card',
+ templateUrl: './mastodon-bot-card.component.html',
+ styleUrls: ['./mastodon-bot-card.component.scss'],
+})
+export class MastodonBotCardComponent implements OnInit {
+ @Input() hoodId;
+ mastodons$;
+
+ constructor(
+ private mastodonService: MastodonService,
+ private dialog: MatDialog
+ ) {}
+
+ ngOnInit(): void {
+ this.mastodons$ = this.mastodonService.getMastodonsPublic(this.hoodId);
+ }
+
+ onInfoClick() {
+ this.dialog.open(MastodonBotInfoDialogComponent);
+ }
+}
diff --git a/frontend/src/app/platforms/mastodon/mastodon-bot-card/mastodon-bot-info-dialog/mastodon-bot-info-dialog.component.html b/frontend/src/app/platforms/mastodon/mastodon-bot-card/mastodon-bot-info-dialog/mastodon-bot-info-dialog.component.html
new file mode 100644
index 0000000..55ea167
--- /dev/null
+++ b/frontend/src/app/platforms/mastodon/mastodon-bot-card/mastodon-bot-info-dialog/mastodon-bot-info-dialog.component.html
@@ -0,0 +1,54 @@
+
+
+
How to communicate with the hood via Telegram?
+
+
+
+ How to subscribe to the hood via Telegram?
+
+
+
+ Click on the telegram bot name that is shown in the telegram card.
+
+
+ Start messaging the telegram bot that the link leads to by writing a
+ message containing only the word /start . Done!
+
+
+
+
+
+ How to send a broadcast message to the hood?
+
+
+ Write a direct message to the bot. This message will be broadcasted to
+ the other platforms.
+
+
+
+
+ How to receive messages?
+
+
+ If you subscribed to the bot, you will automatically receive the
+ messages of your hood from the bot.
+
+
+
+
+ How to stop receiving messages?
+
+
+ Write a message with content /stop to the bot. You
+ should receive a message from the bot which confirms that the
+ unsubscription was successful.
+
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/src/app/platforms/mastodon/mastodon-bot-card/mastodon-bot-info-dialog/mastodon-bot-info-dialog.component.scss b/frontend/src/app/platforms/mastodon/mastodon-bot-card/mastodon-bot-info-dialog/mastodon-bot-info-dialog.component.scss
new file mode 100644
index 0000000..e69de29
diff --git a/frontend/src/app/platforms/mastodon/mastodon-bot-card/mastodon-bot-info-dialog/mastodon-bot-info-dialog.component.ts b/frontend/src/app/platforms/mastodon/mastodon-bot-card/mastodon-bot-info-dialog/mastodon-bot-info-dialog.component.ts
new file mode 100644
index 0000000..0a303ec
--- /dev/null
+++ b/frontend/src/app/platforms/mastodon/mastodon-bot-card/mastodon-bot-info-dialog/mastodon-bot-info-dialog.component.ts
@@ -0,0 +1,12 @@
+import { Component, OnInit } from '@angular/core';
+
+@Component({
+ selector: 'app-mastodon-bot-info-dialog',
+ templateUrl: './mastodon-bot-info-dialog.component.html',
+ styleUrls: ['./mastodon-bot-info-dialog.component.scss']
+})
+export class MastodonBotInfoDialogComponent implements OnInit {
+ constructor() {}
+
+ ngOnInit(): void {}
+}
diff --git a/frontend/src/app/platforms/mastodon/mastodon-settings/mastodon-dialog/mastodon-dialog.component.html b/frontend/src/app/platforms/mastodon/mastodon-settings/mastodon-dialog/mastodon-dialog.component.html
new file mode 100644
index 0000000..f18716c
--- /dev/null
+++ b/frontend/src/app/platforms/mastodon/mastodon-settings/mastodon-dialog/mastodon-dialog.component.html
@@ -0,0 +1,46 @@
+Create new inbox
+
+
+
+
+
+
+ Cancel
+
+ Add Mastodon bot
+
+
diff --git a/frontend/src/app/platforms/mastodon/mastodon-settings/mastodon-dialog/mastodon-dialog.component.scss b/frontend/src/app/platforms/mastodon/mastodon-settings/mastodon-dialog/mastodon-dialog.component.scss
new file mode 100644
index 0000000..dbb2947
--- /dev/null
+++ b/frontend/src/app/platforms/mastodon/mastodon-settings/mastodon-dialog/mastodon-dialog.component.scss
@@ -0,0 +1,26 @@
+.input {
+ display: grid;
+ grid-template-rows: 1fr 1fr 1fr;
+ width: 100%;
+ }
+
+ form {
+ margin-top: 10px;
+ height: 100%;
+ }
+
+ textarea {
+ height: 120px;
+ }
+
+ .example-image {
+ margin-left: 10%;
+ margin-right: 10%;
+ width: 80%;
+ @media screen and (max-width: 600px) {
+ width: 100%;
+ margin-left: 0%;
+ margin-right: 0%;
+ }
+ }
+
\ No newline at end of file
diff --git a/frontend/src/app/platforms/mastodon/mastodon-settings/mastodon-dialog/mastodon-dialog.component.spec.ts b/frontend/src/app/platforms/mastodon/mastodon-settings/mastodon-dialog/mastodon-dialog.component.spec.ts
new file mode 100644
index 0000000..16f7cde
--- /dev/null
+++ b/frontend/src/app/platforms/mastodon/mastodon-settings/mastodon-dialog/mastodon-dialog.component.spec.ts
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { MastodonDialogComponent } from './mastodon-dialog.component';
+
+describe('MastodonDialogComponent', () => {
+ let component: MastodonDialogComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ declarations: [ MastodonDialogComponent ]
+ })
+ .compileComponents();
+
+ fixture = TestBed.createComponent(MastodonDialogComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/frontend/src/app/platforms/mastodon/mastodon-settings/mastodon-dialog/mastodon-dialog.component.ts b/frontend/src/app/platforms/mastodon/mastodon-settings/mastodon-dialog/mastodon-dialog.component.ts
new file mode 100644
index 0000000..162162e
--- /dev/null
+++ b/frontend/src/app/platforms/mastodon/mastodon-settings/mastodon-dialog/mastodon-dialog.component.ts
@@ -0,0 +1,79 @@
+import { Component, OnInit, Inject } from '@angular/core';
+import { Validators, UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
+import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
+import { MastodonService } from 'src/app/core/api';
+import { MatSnackBar } from '@angular/material/snack-bar';
+import { first } from 'rxjs/operators';
+
+@Component({
+ selector: 'app-mastodon-dialog',
+ templateUrl: './mastodon-dialog.component.html',
+ styleUrls: ['./mastodon-dialog.component.scss'],
+})
+export class MastodonDialogComponent implements OnInit {
+ form: UntypedFormGroup;
+
+ constructor(
+ public dialogRef: MatDialogRef,
+ private formBuilder: UntypedFormBuilder,
+ private mastodonService: MastodonService,
+ private snackBar: MatSnackBar,
+ @Inject(MAT_DIALOG_DATA) public data
+ ) {}
+
+ ngOnInit(): void {
+ this.form = this.formBuilder.group({
+ email: ['', Validators.required],
+ password: ['', Validators.required],
+ instance_url: ['', Validators.required],
+ });
+
+ if (this.data.mastodonId) {
+ this.mastodonService
+ .getMastodon(this.data.mastodonId, this.data.hoodId)
+ .subscribe((data) => {
+ this.form.controls.email.setValue(data.email);
+ this.form.controls.password.setValue(data.password);
+ this.form.controls.instance_url.setValue(data.instance_url);
+ });
+ }
+ }
+
+ onCancel() {
+ this.dialogRef.close();
+ }
+
+ success() {
+ this.dialogRef.close();
+ }
+
+ error() {
+ this.snackBar.open('Invalid API Key. Try again!', 'Close', {
+ duration: 2000,
+ });
+ }
+
+ onSubmit() {
+ if (this.form.invalid) {
+ return;
+ }
+
+ const response = {
+ email: this.form.controls.email.value,
+ instance_url: this.form.controls.instance_url.value,
+ password: this.form.controls.password.value
+ }
+
+ this.mastodonService
+ .createMastodon(this.data.hoodId, response)
+ .pipe(first())
+ .subscribe(
+ () => {
+ this.success();
+ },
+ () => {
+ this.error();
+ }
+ );
+}
+}
diff --git a/frontend/src/app/platforms/mastodon/mastodon-settings/mastodon-settings.component.html b/frontend/src/app/platforms/mastodon/mastodon-settings/mastodon-settings.component.html
new file mode 100644
index 0000000..15b5ca1
--- /dev/null
+++ b/frontend/src/app/platforms/mastodon/mastodon-settings/mastodon-settings.component.html
@@ -0,0 +1,69 @@
+
+
+
+
+ Mastodon
+
+ info
+
+
+
+
+
+
+
+
+
+ add
+ Add a platform connection!
+
+
+
+
+
+
+ @{{ mastodon.username }}
+
+
+ more_vert
+
+
+
+
+
+ edit
+ Edit
+
+
+ delete
+ Delete
+
+
+ add
+ Add another
+
+
+
+
+ warning
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/src/app/platforms/mastodon/mastodon-settings/mastodon-settings.component.scss b/frontend/src/app/platforms/mastodon/mastodon-settings/mastodon-settings.component.scss
new file mode 100644
index 0000000..5265644
--- /dev/null
+++ b/frontend/src/app/platforms/mastodon/mastodon-settings/mastodon-settings.component.scss
@@ -0,0 +1,23 @@
+.entry {
+ display: grid;
+ grid-template-columns: 4fr 40px 20px;
+ width: 100%;
+ align-items: center;
+ }
+
+ .platform-title {
+ display: grid;
+ grid-template-columns: 1fr 40px;
+ width: 100%;
+ align-items: center;
+ }
+
+ .platform-heading {
+ align-self: flex-end;
+ }
+
+ .mastodon {
+ background-image: url("../../../../assets/mastodon.png");
+ background-size: cover;
+ }
+
\ No newline at end of file
diff --git a/frontend/src/app/platforms/mastodon/mastodon-settings/mastodon-settings.component.spec.ts b/frontend/src/app/platforms/mastodon/mastodon-settings/mastodon-settings.component.spec.ts
new file mode 100644
index 0000000..d03c29d
--- /dev/null
+++ b/frontend/src/app/platforms/mastodon/mastodon-settings/mastodon-settings.component.spec.ts
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { MastodonSettingsComponent } from './mastodon-settings.component';
+
+describe('MastodonSettingsComponent', () => {
+ let component: MastodonSettingsComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ declarations: [ MastodonSettingsComponent ]
+ })
+ .compileComponents();
+
+ fixture = TestBed.createComponent(MastodonSettingsComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/frontend/src/app/platforms/mastodon/mastodon-settings/mastodon-settings.component.ts b/frontend/src/app/platforms/mastodon/mastodon-settings/mastodon-settings.component.ts
new file mode 100644
index 0000000..ffbd679
--- /dev/null
+++ b/frontend/src/app/platforms/mastodon/mastodon-settings/mastodon-settings.component.ts
@@ -0,0 +1,102 @@
+import { Component, OnInit, Input } from '@angular/core';
+import { MastodonService } from 'src/app/core/api';
+import { Observable } from 'rxjs';
+import { MastodonBotInfoDialogComponent } from '../mastodon-bot-card/mastodon-bot-info-dialog/mastodon-bot-info-dialog.component';
+import { MatDialog } from '@angular/material/dialog';
+import { MastodonDialogComponent } from './mastodon-dialog/mastodon-dialog.component';
+import { YesNoDialogComponent } from 'src/app/shared/yes-no-dialog/yes-no-dialog.component';
+import { MatSnackBar } from '@angular/material/snack-bar';
+
+@Component({
+ selector: 'app-mastodon-settings',
+ templateUrl: './mastodon-settings.component.html',
+ styleUrls: ['./mastodon-settings.component.scss'],
+})
+export class MastodonSettingsComponent implements OnInit {
+ @Input() hoodId;
+ mastodons$: Observable>;
+
+ constructor(
+ private mastodonService: MastodonService,
+ public dialog: MatDialog,
+ private snackBar: MatSnackBar
+ ) {}
+
+ ngOnInit(): void {
+ this.reload();
+ }
+
+ private reload() {
+ this.mastodons$ = this.mastodonService.getMastodons(this.hoodId);
+ }
+
+ onInfoClick() {
+ this.dialog.open(MastodonBotInfoDialogComponent);
+ }
+
+ onDelete(mastodonId) {
+ const dialogRef = this.dialog.open(YesNoDialogComponent, {
+ data: {
+ title: 'Warning',
+ content:
+ 'This will also delete the list of subscribers of the mastodon bot.',
+ },
+ });
+
+ dialogRef.afterClosed().subscribe((response) => {
+ if (response) {
+ this.mastodonService
+ .deleteMastodon(mastodonId, this.hoodId)
+ .subscribe(() => {
+ this.reload();
+ });
+ }
+ });
+ }
+
+ onCreate() {
+ const dialogRef = this.dialog.open(MastodonDialogComponent, {
+ data: { hoodId: this.hoodId },
+ });
+
+ dialogRef.afterClosed().subscribe(() => {
+ this.reload();
+ });
+ }
+
+ onEdit(mastodonId) {
+ const dialogRef = this.dialog.open(MastodonDialogComponent, {
+ data: { hoodId: this.hoodId, mastodonId },
+ });
+
+ dialogRef.afterClosed().subscribe(() => {
+ this.reload();
+ });
+ }
+
+ onChange(mastodon) {
+ if (mastodon.enabled === 0) {
+ this.mastodonService.startMastodon(mastodon.id, this.hoodId).subscribe(
+ () => {},
+ (error) => {
+ this.snackBar.open('Could not start. Check your settings.', 'Close', {
+ duration: 2000,
+ });
+ }
+ );
+ } else if (mastodon.enabled === 1) {
+ this.mastodonService.stopMastodon(mastodon.id, this.hoodId).subscribe(
+ () => {},
+ (error) => {
+ this.snackBar.open('Could not stop. Check your settings.', 'Close', {
+ duration: 2000,
+ });
+ }
+ );
+ }
+ // TODO yeah i know this is bad, implement disabling/enabling
+ setTimeout(() => {
+ this.reload();
+ }, 100);
+ }
+}
diff --git a/frontend/src/app/platforms/platforms-info-page/platforms-info-page.component.html b/frontend/src/app/platforms/platforms-info-page/platforms-info-page.component.html
index b7d8ed4..3d3b646 100644
--- a/frontend/src/app/platforms/platforms-info-page/platforms-info-page.component.html
+++ b/frontend/src/app/platforms/platforms-info-page/platforms-info-page.component.html
@@ -3,4 +3,5 @@
+
diff --git a/frontend/src/app/platforms/platforms.module.ts b/frontend/src/app/platforms/platforms.module.ts
index e84b289..b9bb530 100644
--- a/frontend/src/app/platforms/platforms.module.ts
+++ b/frontend/src/app/platforms/platforms.module.ts
@@ -20,6 +20,10 @@ import { TelegramBotInfoDialogComponent } from './telegram/telegram-bot-card/tel
import { TwitterBotInfoDialogComponent } from './twitter/twitter-bot-card/twitter-bot-info-dialog/twitter-bot-info-dialog.component';
import { EmailConfirmationComponent } from './email/email-confirmation/email-confirmation.component';
import { EmailUnsubscribeComponent } from './email/email-unsubscribe/email-unsubscribe.component';
+import { MastodonBotCardComponent } from './mastodon/mastodon-bot-card/mastodon-bot-card.component';
+import { MastodonSettingsComponent } from './mastodon/mastodon-settings/mastodon-settings.component';
+import { MastodonDialogComponent } from './mastodon/mastodon-settings/mastodon-dialog/mastodon-dialog.component';
+import { MastodonBotInfoDialogComponent } from './mastodon/mastodon-bot-card/mastodon-bot-info-dialog/mastodon-bot-info-dialog.component';
@NgModule({
declarations: [
@@ -42,10 +46,15 @@ import { EmailUnsubscribeComponent } from './email/email-unsubscribe/email-unsub
TwitterBotInfoDialogComponent,
EmailConfirmationComponent,
EmailUnsubscribeComponent,
+ MastodonBotCardComponent,
+ MastodonSettingsComponent,
+ MastodonDialogComponent,
+ MastodonBotInfoDialogComponent
],
imports: [CommonModule, SharedModule],
exports: [
TelegramSettingsComponent,
+ MastodonSettingsComponent,
TwitterSettingsComponent,
EmailSettingsComponent,
PlatformsInfoPageComponent,
diff --git a/frontend/src/assets/mastodon.png b/frontend/src/assets/mastodon.png
new file mode 100644
index 0000000..b09a98b
Binary files /dev/null and b/frontend/src/assets/mastodon.png differ