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
3 changes: 2 additions & 1 deletion api/src/services/api-key.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@ export class ApiKeyService {

if (!apiKey) {
this.logger.warn('Api Key not found');
throw new NotFoundException('Api key not found');
this.logger.info('END: fetchApiKey service');
return null;
}

this.logger.info('END: fetchApiKey service');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,27 @@
</mat-form-field>
<mat-form-field appearance="outline" class="w-full">
<mat-label>Currency</mat-label>
<mat-select class="bg-surface-container-lowest" formControlName="currency">
@for(currency of currencyList; track currency) {
<mat-option [value]="currency.code">{{ currency.text }}</mat-option>

<input
type="text"
matInput
formControlName="currency"
[matAutocomplete]="currencyAuto"
(input)="filterCurrency($event.target.value)"
(blur)="validateCurrency()"
/>

<mat-autocomplete #currencyAuto="matAutocomplete" (optionSelected)="onCurrencySelected($event.option.value)">
@for (currency of filteredCurrencyList; track currency) {
<mat-option [value]="currency.code">
{{ currency.text }} ({{ currency.code }})
</mat-option>
}
</mat-select>
<mat-error>Currency is required</mat-error>
</mat-autocomplete>

<mat-error *ngIf="createOrganizationForm.get('currency')?.hasError('required')">
Currency is required
</mat-error>
</mat-form-field>
<mat-form-field appearance="outline" class="w-full">
<mat-label>External Id</mat-label>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { RxFormBuilder } from '@rxweb/reactive-form-validators';
import { Router } from '@angular/router';
import { CreateOrganizationDto } from '../../../../dtos/organization.dto';
import { instanceToPlain } from 'class-transformer';
import { MatAutocomplete, MatAutocompleteModule } from "@angular/material/autocomplete";

@Component({
selector: 'app-create-organization',
Expand All @@ -20,8 +21,10 @@ import { instanceToPlain } from 'class-transformer';
MatInputModule,
MatIconModule,
MatSelectModule,
CommonModule
],
CommonModule,
MatAutocomplete,
MatAutocompleteModule,
],
templateUrl: './create-organization.component.html',
styleUrls: ['./create-organization.component.css'],
})
Expand All @@ -31,6 +34,8 @@ export class CreateOrganizationComponent implements OnInit {

createOrganizationStore = inject(CreateOrganizationStore);
isNextClicked = this.createOrganizationStore.onNext;
filteredCurrencyList = this.currencyList;


constructor(private formBuilder: RxFormBuilder, private router: Router) {
this.createOrganizationForm = formBuilder.formGroup(new CreateOrganizationDto())
Expand Down Expand Up @@ -63,4 +68,35 @@ export class CreateOrganizationComponent implements OnInit {
}
})
}

filterCurrency(value: string) {
const search = value.toLowerCase();

this.filteredCurrencyList = this.currencyList.filter(
(currency: any) =>
currency.text.toLowerCase().includes(search) ||
currency.code.toLowerCase().includes(search)
);
}

onCurrencySelected(code: string) {
this.createOrganizationForm.get('currency')?.setValue(code);
}

validateCurrency() {
const value = this.createOrganizationForm.get('currency')?.value;

if (!value) return;

const exists = this.currencyList.some(
(c: any) =>
c.code === value||
c.text === value
);

if (!exists) {
this.createOrganizationForm.get('currency')?.setValue('');
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
data.onDelete()
"
>
Delete
{{ data.buttonText || 'Delete' }}
</button>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<div>
<div class="!w-[312px]" disableClose>
<div class="pt-[30px] pr-[24px] pl-[16px] pb-[12px]">
<div class="pt-[30px] pr-[16px] pl-[16px] pb-[12px]">
<mat-nav-list class="cursor-pointer flex flex-col gap-[4px]">
<a routerLink="./dashboard" routerLinkActive #dashboard="routerLinkActive"
[routerLinkActiveOptions]="{ exact: false }">
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/app/components/home/home.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

<div class="flex">
<app-sidenav class="fixed top-20 h-[calc(100vh-5rem)] z-40"></app-sidenav>
<div class="ml-[252px] px-[48px] w-full mt-[80px] py-[40px] overflow-y-auto h-[calc(100vh-5rem)]">
<div class="ml-[304px] px-[48px] w-full mt-[80px] py-[40px] overflow-y-auto h-[calc(100vh-5rem)]">
<router-outlet></router-outlet>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
<!-- Header -->
<div class="mb-5">
<div class="mat-body-large text-on-surface">API Keys</div>
</div>

<!-- API Key Section -->
<mat-card appearance="outlined" class="bg-surface-container-lowest w-3/6">
<mat-card-content>
<div class="flex flex-col">

<!-- Body -->
<div class="flex flex-col p-6 gap-6">

@if (apiKeysStore.isLoading()) {

<!-- Loading Skeleton -->
<div class="flex flex-col gap-1">
<div class="mat-body-medium text-on-surface-variant">API Key</div>
<ngx-skeleton-loader
[theme]="{ height: '24px', width: '300px', margin: '0', padding: '0' }">
</ngx-skeleton-loader>
</div>

<div class="flex flex-col gap-1">
<div class="mat-body-medium text-on-surface-variant">Created At</div>
<ngx-skeleton-loader
[theme]="{ height: '24px', width: '200px', margin: '0', padding: '0' }">
</ngx-skeleton-loader>
</div>

} @else if (!apiKeysStore.apiKey()) {

<!-- No API Key State -->
<div class="flex flex-col items-center justify-center py-8 gap-4">
<div class="scale-150 rounded-full inline-flex justify-start items-center gap-2.5 overflow-hidden bg-surface-container p-3">
<mat-icon class="material-symbols-outlined">vpn_key</mat-icon>
</div>
<div class="text-center">
<div class="mat-title-medium text-on-surface mb-2">No API Key Generated</div>
<div class="mat-body-medium text-on-surface-variant">
Generate an API key to start integrating with our services
</div>
</div>
</div>

} @else {

<!-- API Key Display -->
<div class="flex flex-col gap-1">
<div class="mat-body-medium text-on-surface-variant">API Key</div>
<div class="flex items-center gap-2">
<div class="mat-body-large text-on-surface font-mono bg-surface-container px-3 py-2 rounded flex-1 break-all">
{{ apiKeysStore.apiKey()?.key }}
</div>
<button mat-icon-button (click)="copyToClipboard(apiKeysStore.apiKey()?.key || '')"
matTooltip="Copy to clipboard">
<mat-icon class="material-symbols-outlined">content_copy</mat-icon>
</button>
</div>
</div>

<!-- Secret Display (only shown after generation) -->
@if (showSecret() && apiKeysStore.apiKey()?.secret) {
<div class="flex flex-col gap-1">
<div class="mat-body-medium text-on-surface-variant">Secret Key</div>
<div class="flex items-center gap-2">
<div class="mat-body-large text-on-surface font-mono bg-surface-container px-3 py-2 rounded flex-1 break-all">
{{ apiKeysStore.apiKey()?.secret }}
</div>
<button mat-icon-button (click)="copyToClipboard(apiKeysStore.apiKey()?.secret || '')"
matTooltip="Copy to clipboard">
<mat-icon class="material-symbols-outlined">content_copy</mat-icon>
</button>
</div>
<div class="mat-body-small text-error mt-1 flex items-start gap-1">
<mat-icon class="material-symbols-outlined -mt-1 text-base">warning</mat-icon>
<span>Save this secret key now. You won't be able to see it again!</span>
</div>
</div>

<!-- Download Button -->
<div class="flex justify-end">
<button mat-flat-button
class="bg-primary text-on-primary rounded-full px-6 py-2"
(click)="downloadTxt(apiKeysStore.apiKey()!)">
<mat-icon class="material-symbols-outlined mr-2">download</mat-icon>
Download Credentials
</button>
</div>
}

<!-- Created At -->
<div class="flex flex-col gap-1">
<div class="mat-body-medium text-on-surface-variant">Generated at</div>
<div class="mat-body-large text-on-surface">
{{ (apiKeysStore.apiKey()?.created_at ?? '') | custom_date : "" : true }}
</div>
</div>

}

</div>

<!-- Footer -->
<div>
<mat-divider class="border-outline-variant"></mat-divider>

@if (!apiKeysStore.apiKey() && !apiKeysStore.isLoading()) {
<!-- Generate Button -->
<button mat-button
class="flex h-[56px] w-full justify-start rounded-none"
(click)="onGenerateApiKey()">
<mat-icon class="material-symbols-outlined mr-2">add</mat-icon>
Generate API Key
</button>
} @else if (apiKeysStore.apiKey()) {
<!-- Regenerate Button -->
<button mat-button
class="text-error flex h-[56px] w-full justify-start rounded-none"
(click)="onRegenerateApiKey()">
<mat-icon class="material-symbols-outlined mr-2">refresh</mat-icon>
Regenerate API Key
</button>
}
</div>

</div>
</mat-card-content>
</mat-card>

<!-- Information Section -->
<div class="mt-6 w-3/6">
<div class="flex items-start gap-2 p-4 bg-surface-container rounded">
<mat-icon class="material-symbols-outlined text-primary">info</mat-icon>
<div class="mat-body-medium text-on-surface-variant">
<div class="font-semibold mb-1">API Key Usage</div>
<ul class="list-disc list-inside space-y-1 text-sm">
<li>Use this API key to authenticate your requests</li>
<li>Keep your API key and secret secure</li>
<li>Regenerating will invalidate the previous key</li>
</ul>
</div>
</div>
</div>
Loading