Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: Donate to educator account #15

Merged
merged 4 commits into from
Jul 12, 2024
Merged
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
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<label class="toggle" for="input{{random()}}">
<label class="toggle dark:text-white text-gray-900" for="input{{random()}}">
<input class="toggle-checkbox" type="checkbox" id="input{{random()}}" [checked]="value()" (change)="value.set(input.checked)" #input>
<div class="toggle-switch"></div>
<span class="toggle-label">{{description()}}</span>
Expand Down
27 changes: 22 additions & 5 deletions src/app/pages/transfer/transfer.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ <h1 class="mb-4 text-4xl font-extrabold leading-none tracking-tight md:text-5xl
<div class="max-w-screen-xl px-4 py-8 mx-auto space-y-12 lg:space-y-20 lg:py-24 lg:px-6">
<div class="items-center gap-8 lg:grid xl:gap-16">
<div class="text-gray-500 sm:text-lg dark:text-gray-400">
<p class="mb-4"><transloco key="transfer.visit_old" [params]="{oldUrl: '/transfer'}" /></p>
<p class="mb-6"><transloco key="transfer.visit_old" [params]="{oldUrl: '/transfer'}" /></p>
<form class="max-w-sm mx-auto" [formGroup]="form" (ngSubmit)="transfer()">
<div class="mb-2">
<label for="source_api_key" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">{{'transfer.source_api_key' | transloco}}</label>
<label for="source_api_key" class="block mb-2 font-medium text-gray-900 dark:text-white">{{'transfer.source_api_key' | transloco}}</label>
<input formControlName="apiKey" type="text" id="source_api_key" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" required [placeholder]="'transfer.source_api_key.description' | transloco" />
</div>
@if (form.value.apiKeyValidated === false) {
Expand All @@ -29,16 +29,28 @@ <h1 class="mb-4 text-4xl font-extrabold leading-none tracking-tight md:text-5xl
<app-toggle-checkbox [description]="'transfer.source_api_key.remember' | transloco" formControlName="remember" />
</div>

<div [class.mb-5]="form.value.targetUserValidated !== false" [class.mb-1]="form.value.targetUserValidated === false">
<label for="target_user" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">{{'transfer.target_user' | transloco}}</label>
<div [class.mb-1]="educatorAccounts()" [class.mb-5]="!educatorAccounts()">
<label for="target_user" class="block mb-2 font-medium text-gray-900 dark:text-white">{{'transfer.target_user' | transloco}}</label>
<input formControlName="targetUser" type="text" id="target_user" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" required [placeholder]="'transfer.target_user.description' | transloco:{exampleUser: exampleUser()}" />
</div>
@if (educatorAccounts()) {
<div class="dark:text-white text-gray-900" [class.mb-5]="form.value.targetUserValidated !== false" [class.mb-1]="form.value.targetUserValidated === false">
<p class="mb-1"><small>{{'transfer.donate' | transloco}}<sup><a routerLink="/transfer-v2" fragment="note1" class="text-blue-600 dark:text-green-400">1</a></sup></small></p>

<select formControlName="educatorAccount" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500">
@for (user of educatorAccounts(); track user.id) {
<option [value]="null">{{'option.empty' | transloco}}</option>
<option [value]="user.id">{{user.username}} ({{'transfer.account.kudos_amount' | transloco:{amountFormatted:user.kudos | formatNumber} }})</option>
}
</select>
</div>
}
@if (form.value.targetUserValidated === false) {
<p class="mb-5 text-red"><small>{{'invalid_target_user' | transloco}}</small></p>
}

<div [class.mb-5]="form.value.kudosAmountValidated !== false" [class.mb-1]="form.value.kudosAmountValidated === false">
<label for="kudos_amount" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">{{'transfer.kudos_amount' | transloco}}</label>
<label for="kudos_amount" class="block mb-2 font-medium text-gray-900 dark:text-white">{{'transfer.kudos_amount' | transloco}}</label>
<input formControlName="kudosAmount" type="number" min="1" id="kudos_amount" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" required [placeholder]="'transfer.kudos_amount.description' | transloco:{user: form.value.targetUser || ('transfer.kudos_amount.description.user_placeholder' | transloco)}" />
</div>
@if (form.value.kudosAmountValidated === false) {
Expand All @@ -62,6 +74,11 @@ <h1 class="mb-4 text-4xl font-extrabold leading-none tracking-tight md:text-5xl
[class.text-white]="form.valid"
type="submit" class="hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm w-full sm:w-auto px-5 py-2.5 text-center dark:hover:bg-blue-700 dark:focus:ring-blue-800">Submit</button>
</form>

<p id="note1" class="mt-6" [class.active]="fragment() === 'note1'">
<sup>1</sup>
{{'educators.explanation' | transloco}}
</p>
</div>
</div>
</div>
Expand Down
43 changes: 40 additions & 3 deletions src/app/pages/transfer/transfer.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ import {debounceTime} from "rxjs";
import {Subscriptions} from "../../helper/subscriptions";
import {AiHordeService} from "../../services/ai-horde.service";
import {HordeUser} from "../../types/horde-user";
import {toSignal} from "@angular/core/rxjs-interop";
import {FormatNumberPipe} from "../../pipes/format-number.pipe";
import {ActivatedRoute, RouterLink} from "@angular/router";

@Component({
selector: 'app-transfer',
Expand All @@ -25,7 +28,9 @@ import {HordeUser} from "../../types/horde-user";
TranslocoPipe,
ToggleCheckboxComponent,
ReactiveFormsModule,
JsonPipe
JsonPipe,
FormatNumberPipe,
RouterLink
],
templateUrl: './transfer.component.html',
styleUrl: './transfer.component.scss'
Expand All @@ -50,14 +55,18 @@ export class TransferComponent implements OnInit, OnDestroy {
}

return this.currentUser()!.kudos;
})
});
public educatorAccounts = toSignal(this.aiHorde.getEducatorAccounts());
public fragment = signal<string|null>(null);

public form = new FormGroup({
apiKey: new FormControl<string>('', [Validators.required]),
remember: new FormControl<boolean>(false),
targetUser: new FormControl<string>('', [Validators.required]),
kudosAmount: new FormControl<number>(1, [Validators.required, Validators.min(1)]),

educatorAccount: new FormControl<number | null>(null),

apiKeyValidated: new FormControl<boolean | null>(null, [Validators.requiredTrue]),
targetUserValidated: new FormControl<boolean | null>(null, [Validators.requiredTrue]),
kudosAmountValidated: new FormControl<boolean | null>(null, [Validators.requiredTrue]),
Expand All @@ -69,6 +78,7 @@ export class TransferComponent implements OnInit, OnDestroy {
private readonly footerColor: FooterColorService,
private readonly database: DatabaseService,
private readonly aiHorde: AiHordeService,
public readonly activatedRoute: ActivatedRoute,
) {
}

Expand All @@ -89,6 +99,26 @@ export class TransferComponent implements OnInit, OnDestroy {
kudosAmount ??= 0;
this.form.patchValue({kudosAmountValidated: this.maximumKudos() !== null && kudosAmount <= this.maximumKudos()!});
}));
this.subscriptions.add(this.form.controls.educatorAccount.valueChanges.subscribe(accountId => {
// @ts-ignore
if (accountId === 'null') {
accountId = null;
}

if (!accountId) {
this.form.controls.targetUser.enable();
this.form.patchValue({targetUser: ''});
return;
}

// @ts-ignore
accountId = Number(accountId);
const account = this.educatorAccounts()!.filter(account => account.id === accountId)[0];

this.form.controls.targetUser.disable();
this.form.patchValue({targetUser: account.username});
}));

this.subscriptions.add(this.form.controls.apiKey.valueChanges.pipe(
debounceTime(500)
).subscribe(async apiKey => {
Expand Down Expand Up @@ -134,6 +164,13 @@ export class TransferComponent implements OnInit, OnDestroy {

this.sentSuccessfully.set(null);
}));

this.subscriptions.add(this.activatedRoute.fragment.subscribe(fragment => {
this.fragment.set(fragment);
if (fragment) {
document.querySelector(`#${fragment}`)?.scrollIntoView();
}
}));
}

public ngOnDestroy(): void {
Expand All @@ -148,7 +185,7 @@ export class TransferComponent implements OnInit, OnDestroy {

const success = await toPromise(this.aiHorde.transferKudos(
this.form.value.apiKey!,
this.form.value.targetUser!,
this.form.controls.targetUser.value!,
this.form.value.kudosAmount!,
));
this.sentSuccessfully.set(success);
Expand Down
13 changes: 12 additions & 1 deletion src/app/services/ai-horde.service.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {Injectable} from '@angular/core';
import {HttpClient} from "@angular/common/http";
import {catchError, map, Observable, of} from "rxjs";
import {catchError, map, Observable, of, zip} from "rxjs";
import {ImageTotalStats} from "../types/image-total-stats";
import {HordePerformance} from "../types/horde-performance";
import {TextTotalStats} from "../types/text-total-stats";
Expand Down Expand Up @@ -107,4 +107,15 @@ export class AiHordeService {
catchError(() => of(false)),
);
}

public getEducatorAccounts(): Observable<HordeUser[]> {
// todo once filtering is available, filter it on the api, this is only temporary solution
const userIds = [258170];

return zip(
userIds.map(userId => this.getUserById(userId).pipe(
map(user => user!),
))
)
}
}
8 changes: 6 additions & 2 deletions src/assets/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@
"transfer": "Transfer kudos",
"transfer.visit_old": "If you would like to log in using one of the account providers (Google, Discord, Github), you might need to use [link:oldUrl]the old version of the transfer page[/link].",
"transfer.source_api_key": "Your API key",
"transfer.target_user": "Unique user ID",
"transfer.target_user": "The receiving user",
"transfer.target_user.description": "For example: {{exampleUser}}",
"transfer.source_api_key.description": "Type the API key that belongs to your user",
"transfer.source_api_key.remember": "Remember the API key?",
Expand All @@ -87,5 +87,9 @@
"transfer.kudos_amount.description.user_placeholder": "the user",
"transfer.success": "The kudos have been successfully transferred!",
"transfer.error": "There was an error while transferring the kudos, please try again later.",
"transfer.too_many_kudos": "Eh, you don't have that many kudos"
"transfer.too_many_kudos": "Eh, you don't have that many kudos",
"transfer.donate": "or donate them to one of the educator accounts:",
"option.empty": "-- none --",
"transfer.account.kudos_amount": "has {{amountFormatted}} kudos",
"educators.explanation": "An educator account provides teachers and educational institutions with access to AI Horde for generating AI images."
}
Loading