diff --git a/ClientApp/src/app/app.component.scss b/ClientApp/src/app/app.component.scss
index a4add75..bade6fd 100644
--- a/ClientApp/src/app/app.component.scss
+++ b/ClientApp/src/app/app.component.scss
@@ -20,24 +20,11 @@
}
}
-// RTL & Font setup
+// Application font
* {
- font-family: Vazir, system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
+ font-family: Roboto, system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
}
-// Material theme setup
.mat-typography {
- font-family: Vazir, system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
+ font-family: Roboto, system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
}
-
-// Material Components RTL setup
-[dir='rtl'] {
- .mat-form-field-appearance-outline {
- .mat-form-field-outline-start {
- border-radius: 0 4px 4px 0;
- }
- .mat-form-field-outline-end {
- border-radius: 4px 0 0 4px;
- }
- }
-}
\ No newline at end of file
diff --git a/ClientApp/src/app/app.component.spec.ts b/ClientApp/src/app/app.component.spec.ts
index 520887f..93cbb8a 100644
--- a/ClientApp/src/app/app.component.spec.ts
+++ b/ClientApp/src/app/app.component.spec.ts
@@ -1,7 +1,44 @@
import { ApiBaseSettings } from './environmets/ApiSettings';
+import { AuthenticationModel } from './environmets/AuthenticationModel';
describe('API configuration', () => {
it('uses a same-origin API URL', () => {
expect(new ApiBaseSettings().baseUrl).toBe('/api');
});
});
+
+describe('AuthenticationModel', () => {
+ const authentication = new AuthenticationModel();
+
+ afterEach(() => localStorage.clear());
+
+ it('accepts a token that has not expired', () => {
+ authentication.Set(createToken(Math.floor(Date.now() / 1000) + 60), 'test-user');
+
+ expect(authentication.IsUserLogin()).toBeTrue();
+ expect(authentication.GetUserName()).toBe('test-user');
+ });
+
+ it('clears an expired token instead of keeping the user in a login loop', () => {
+ authentication.Set(createToken(Math.floor(Date.now() / 1000) - 60), 'test-user');
+
+ expect(authentication.IsUserLogin()).toBeFalse();
+ expect(authentication.GetToken()).toBeNull();
+ expect(authentication.GetUserName()).toBeNull();
+ });
+
+ it('clears malformed tokens', () => {
+ authentication.Set('not-a-jwt', 'test-user');
+
+ expect(authentication.IsUserLogin()).toBeFalse();
+ expect(authentication.GetToken()).toBeNull();
+ });
+});
+
+function createToken(exp: number): string {
+ const payload = btoa(JSON.stringify({ exp }))
+ .replace(/\+/g, '-')
+ .replace(/\//g, '_')
+ .replace(/=+$/, '');
+ return `header.${payload}.signature`;
+}
diff --git a/ClientApp/src/app/authentication/login/login.component.html b/ClientApp/src/app/authentication/login/login.component.html
index 370f475..59cc56f 100644
--- a/ClientApp/src/app/authentication/login/login.component.html
+++ b/ClientApp/src/app/authentication/login/login.component.html
@@ -1,23 +1,24 @@
-
+
\ No newline at end of file
+
diff --git a/ClientApp/src/app/authentication/register/register.component.html b/ClientApp/src/app/authentication/register/register.component.html
index 0f69e74..fe89b0a 100644
--- a/ClientApp/src/app/authentication/register/register.component.html
+++ b/ClientApp/src/app/authentication/register/register.component.html
@@ -1,31 +1,31 @@
-
+
- ثبت نام
+ Create an account
-
\ No newline at end of file
+
diff --git a/ClientApp/src/app/components/add-note/add-note.component.html b/ClientApp/src/app/components/add-note/add-note.component.html
index cd2dc6d..97deca5 100644
--- a/ClientApp/src/app/components/add-note/add-note.component.html
+++ b/ClientApp/src/app/components/add-note/add-note.component.html
@@ -2,25 +2,25 @@
- افزودن جزوه جدید
+ Create a note
\ No newline at end of file
+
diff --git a/ClientApp/src/app/components/home/home.component.scss b/ClientApp/src/app/components/home/home.component.scss
index 72dd2e4..44181f6 100644
--- a/ClientApp/src/app/components/home/home.component.scss
+++ b/ClientApp/src/app/components/home/home.component.scss
@@ -4,7 +4,6 @@
position: relative;
overflow: hidden;
background: #f8f9fa;
- direction: rtl;
}
.background-pattern {
@@ -140,12 +139,6 @@
}
}
- // Add Persian/Arabic font support
- @font-face {
- font-family: 'Vazir';
- src: url('../../../assets/Vazir.woff2') format('woff2');
- }
-
:host {
- font-family: 'Vazir', system-ui, -apple-system, sans-serif;
- }
\ No newline at end of file
+ font-family: Roboto, system-ui, -apple-system, sans-serif;
+ }
diff --git a/ClientApp/src/app/components/newnote/newnote.component.html b/ClientApp/src/app/components/newnote/newnote.component.html
index bfe3199..a6e5e37 100644
--- a/ClientApp/src/app/components/newnote/newnote.component.html
+++ b/ClientApp/src/app/components/newnote/newnote.component.html
@@ -2,8 +2,8 @@
- افزودن موضوع نوت
- لطفاً اطلاعات نوت خود را وارد کنید
+ Create a category
+ Enter a name and an optional description.
@@ -13,23 +13,23 @@
autocomplete="off">
- موضوع
+ Category name
+ placeholder="Enter a category name">
- موضوع اجباری است
+ A category name is required
- موضوع باید حداقل ۳ حرف باشد
+ The category name must contain at least 3 characters
- توضیحات
+ Description
@@ -60,6 +60,6 @@
+ message="Creating category...">
-
\ No newline at end of file
+
diff --git a/ClientApp/src/app/components/newnote/newnote.component.scss b/ClientApp/src/app/components/newnote/newnote.component.scss
index ae4e1a9..21b860f 100644
--- a/ClientApp/src/app/components/newnote/newnote.component.scss
+++ b/ClientApp/src/app/components/newnote/newnote.component.scss
@@ -63,11 +63,6 @@
}
}
- // RTL Support
- :host {
- direction: rtl;
- }
-
// Responsive Design
@media (max-width: 768px) {
.create-note-container {
@@ -99,4 +94,4 @@
justify-content: center;
}
}
- }
\ No newline at end of file
+ }
diff --git a/ClientApp/src/app/components/newnote/newnote.component.ts b/ClientApp/src/app/components/newnote/newnote.component.ts
index 1cfda46..3cfd216 100644
--- a/ClientApp/src/app/components/newnote/newnote.component.ts
+++ b/ClientApp/src/app/components/newnote/newnote.component.ts
@@ -94,9 +94,10 @@ export class NewNoteComponent implements OnInit {
}
},
error:(err)=>{
- if(err.status == 401)
+ this.isLoading = false;
+ if(err.status !== 401)
{
- this.router.navigate(['sign-in'])
+ this.showErrorMessage();
}
console.log(err);
}
@@ -110,20 +111,19 @@ export class NewNoteComponent implements OnInit {
}
private showSuccessMessage(): void {
- this.snackBar.open('نوت با موفقیت ذخیره شد', 'باشه', {
+ this.isLoading = false;
+ this.snackBar.open('Category created successfully.', 'OK', {
duration: 3000,
horizontalPosition: 'start',
- direction: 'rtl',
panelClass: 'success-snackbar'
});
this.router.navigate(['note-list'])
}
private showErrorMessage(): void {
- this.snackBar.open('خطا در ذخیره نوت', 'تلاش مجدد', {
+ this.snackBar.open('Could not create the category.', 'Try again', {
duration: 5000,
horizontalPosition: 'start',
- direction: 'rtl',
panelClass: 'error-snackbar'
});
}
@@ -133,4 +133,4 @@ export class NewNoteComponent implements OnInit {
class CreateNoteSettingDto {
topic='';
description ='';
-}
\ No newline at end of file
+}
diff --git a/ClientApp/src/app/components/note-detail/note-detail.component.html b/ClientApp/src/app/components/note-detail/note-detail.component.html
index 4a5cd95..f2e8377 100644
--- a/ClientApp/src/app/components/note-detail/note-detail.component.html
+++ b/ClientApp/src/app/components/note-detail/note-detail.component.html
@@ -20,8 +20,8 @@
-
تاریخ ایجاد
-
{{ getPersianDate() }}
+
Created
+
{{ getFormattedDate() }}
@@ -32,7 +32,7 @@
@@ -40,19 +40,19 @@
-
فایلهای پیوست شده
+
Attachments
-
+
diff --git a/ClientApp/src/app/components/note-detail/note-detail.component.scss b/ClientApp/src/app/components/note-detail/note-detail.component.scss
index 5cf567d..b67cff6 100644
--- a/ClientApp/src/app/components/note-detail/note-detail.component.scss
+++ b/ClientApp/src/app/components/note-detail/note-detail.component.scss
@@ -229,21 +229,6 @@ $error-text: #dc2626; // red-600
}
}
-// RTL Support
-[dir="rtl"] {
- .content-grid {
- .date-card {
- .date-content {
- flex-direction: row-reverse;
- }
- }
- }
-
- .link-section {
- flex-direction: row-reverse;
- }
-}
-
// Print Styles
@media print {
.note-container {
@@ -279,4 +264,4 @@ $error-text: #dc2626; // red-600
.card {
border: 1px solid currentColor;
}
-}
\ No newline at end of file
+}
diff --git a/ClientApp/src/app/components/note-detail/note-detail.component.ts b/ClientApp/src/app/components/note-detail/note-detail.component.ts
index ab51974..5364d42 100644
--- a/ClientApp/src/app/components/note-detail/note-detail.component.ts
+++ b/ClientApp/src/app/components/note-detail/note-detail.component.ts
@@ -37,8 +37,12 @@ export class NoteDetailComponent implements OnInit, OnDestroy {
return this.noteItem.searchWords?.split(',').filter(Boolean) || [];
}
- public getPersianDate(): string {
- return new Date(this.noteItem.creationDate).toLocaleDateString('fa-IR');
+ public getFormattedDate(): string {
+ return new Date(this.noteItem.creationDate).toLocaleDateString('en-US', {
+ year: 'numeric',
+ month: 'long',
+ day: 'numeric'
+ });
}
private loadNoteItem(noteId: string): void {
@@ -46,7 +50,7 @@ export class NoteDetailComponent implements OnInit, OnDestroy {
.subscribe({
next: note => this.loadFiles(note),
error: () => {
- this.error = 'خطا در دریافت اطلاعات جزوه';
+ this.error = 'Could not load the note.';
this.loading = false;
}
});
@@ -70,7 +74,7 @@ export class NoteDetailComponent implements OnInit, OnDestroy {
this.loading = false;
},
error: () => {
- this.error = 'خطا در دریافت فایلهای پیوست';
+ this.error = 'Could not load the attached files.';
this.loading = false;
}
});
diff --git a/ClientApp/src/app/components/note-list-component/note-list.component.html b/ClientApp/src/app/components/note-list-component/note-list.component.html
index 46fd5fb..4eb74f7 100644
--- a/ClientApp/src/app/components/note-list-component/note-list.component.html
+++ b/ClientApp/src/app/components/note-list-component/note-list.component.html
@@ -3,10 +3,10 @@
@@ -14,16 +14,16 @@
note موضوعات
- جستجو
+ Search
+ placeholder="Search categories...">
search
- تعداد نمایش در هر صفحه
+ Items per page
{{ size }}
@@ -43,7 +43,7 @@ {{ note.topic }}
{{ note.creationDate | date:'medium' }}
format_indent_decrease
- جزئیات
+ Details
@@ -52,7 +52,7 @@
{{ note.topic }}
- موردی یافت نشد
+ No categories found
@@ -65,6 +65,6 @@ {{ note.topic }}
[pageSizeOptions]="pageSizeOptions"
[pageIndex]="currentPage"
(page)="onPageChange($event)"
- aria-label="انتخاب صفحه">
+ aria-label="Select page">
-
\ No newline at end of file
+
diff --git a/ClientApp/src/app/components/topic-notes/topic-notes.component.html b/ClientApp/src/app/components/topic-notes/topic-notes.component.html
index 127338d..2e38ac1 100644
--- a/ClientApp/src/app/components/topic-notes/topic-notes.component.html
+++ b/ClientApp/src/app/components/topic-notes/topic-notes.component.html
@@ -3,11 +3,11 @@
@@ -32,8 +32,8 @@ {{ topicName }}
- جزئیات
- chevron_left
+ Details
+ chevron_right
@@ -45,7 +45,7 @@ {{ topicName }}
-
هیچ نوتی برای این موضوع یافت نشد
+
No notes were found in this category.
-
\ No newline at end of file
+
diff --git a/ClientApp/src/app/coreComponents/loading/loadingoverlay.component .ts b/ClientApp/src/app/coreComponents/loading/loadingoverlay.component .ts
index d795b81..534316c 100644
--- a/ClientApp/src/app/coreComponents/loading/loadingoverlay.component .ts
+++ b/ClientApp/src/app/coreComponents/loading/loadingoverlay.component .ts
@@ -83,5 +83,5 @@ import { MatSnackBarModule } from '@angular/material/snack-bar';
]
})
export class LoadingOverlayComponent {
- @Input() message = 'در حال پردازش...';
-}
\ No newline at end of file
+ @Input() message = 'Processing...';
+}
diff --git a/ClientApp/src/app/coreComponents/navigation/navigation.component.scss b/ClientApp/src/app/coreComponents/navigation/navigation.component.scss
index b64eaae..74dfade 100644
--- a/ClientApp/src/app/coreComponents/navigation/navigation.component.scss
+++ b/ClientApp/src/app/coreComponents/navigation/navigation.component.scss
@@ -84,7 +84,6 @@
justify-content: center;
color: white;
font-size: 20px;
- direction: rtl;
transition: all 0.3s ease;
&:hover {
diff --git a/ClientApp/src/app/coreComponents/navigation/navigation.component.ts b/ClientApp/src/app/coreComponents/navigation/navigation.component.ts
index 11be454..ee66749 100644
--- a/ClientApp/src/app/coreComponents/navigation/navigation.component.ts
+++ b/ClientApp/src/app/coreComponents/navigation/navigation.component.ts
@@ -36,11 +36,11 @@ export class NavigationComponent {
isMenuVisible = false;
currentRotation = 0;
menuItems = [
- { label: 'خانه', route: '/home' },
- { label: 'نوتها', route: '/note-list' },
- { label: 'نوت جدید', route: '/new-note' },
- { label: 'لاگین', route: '/sign-in' },
- { label: 'ثبت نام', route: '/sign-up' }
+ { label: 'Home', route: '/home' },
+ { label: 'Notes', route: '/note-list' },
+ { label: 'New category', route: '/new-note' },
+ { label: 'Sign in', route: '/sign-in' },
+ { label: 'Sign up', route: '/sign-up' }
];
constructor(
diff --git a/ClientApp/src/app/environmets/AuthenticationModel.ts b/ClientApp/src/app/environmets/AuthenticationModel.ts
index 8be9c76..a50a986 100644
--- a/ClientApp/src/app/environmets/AuthenticationModel.ts
+++ b/ClientApp/src/app/environmets/AuthenticationModel.ts
@@ -34,12 +34,35 @@ export class AuthenticationModel{
}
IsUserLogin()
{
- var token = localStorage.getItem("TokenAuth");
- if(token !== '' && token !== null)
- {
+ const token = this.GetToken();
+ if (!token) {
+ return false;
+ }
+
+ try {
+ const payloadPart = token.split('.')[1];
+ if (!payloadPart) {
+ this.LogOut();
+ return false;
+ }
+
+ const normalizedPayload = payloadPart
+ .replace(/-/g, '+')
+ .replace(/_/g, '/')
+ .padEnd(Math.ceil(payloadPart.length / 4) * 4, '=');
+ const payload = JSON.parse(atob(normalizedPayload));
+ const expiresAt = Number(payload.exp) * 1000;
+
+ if (!Number.isFinite(expiresAt) || expiresAt <= Date.now()) {
+ this.LogOut();
+ return false;
+ }
+
return true;
+ } catch {
+ this.LogOut();
+ return false;
}
- return false;
}
LogOut()
{
diff --git a/ClientApp/src/app/services/base.api.ts b/ClientApp/src/app/services/base.api.ts
index 6e14feb..f9d300a 100644
--- a/ClientApp/src/app/services/base.api.ts
+++ b/ClientApp/src/app/services/base.api.ts
@@ -1,5 +1,7 @@
-import { HttpClient, HttpHeaders } from '@angular/common/http';
+import { HttpClient, HttpErrorResponse, HttpHeaders } from '@angular/common/http';
import { Injectable } from '@angular/core';
+import { Router } from '@angular/router';
+import { catchError, throwError } from 'rxjs';
import { ApiBaseSettings } from '../environmets/ApiSettings';
import { AuthenticationModel } from '../environmets/AuthenticationModel';
@@ -11,7 +13,11 @@ export class ApiService {
apiSetting = new ApiBaseSettings();
baseUrl = this.apiSetting.baseUrl;
- constructor(private http: HttpClient, private authentication:AuthenticationModel) { }
+ constructor(
+ private http: HttpClient,
+ private authentication: AuthenticationModel,
+ private router: Router
+ ) { }
get(url: string, params?: any) {
let headers = new HttpHeaders({ 'Content-Type': 'application/json' });
@@ -27,7 +33,8 @@ export class ApiService {
params,
headers: headers
};
- return this.http.get(this.getUrl(url), httpOptions);
+ return this.http.get(this.getUrl(url), httpOptions)
+ .pipe(catchError(error => this.handleError(error)));
}
post(url: string, params: any, contentType='application/json') {
@@ -43,7 +50,8 @@ export class ApiService {
'Bearer ' + this.authentication.GetToken()
);
}
- return this.http.post(this.getUrl(url), params, {headers:headers});
+ return this.http.post(this.getUrl(url), params, {headers:headers})
+ .pipe(catchError(error => this.handleError(error)));
}
put(url: string, params: any) {
@@ -55,7 +63,8 @@ export class ApiService {
'Bearer ' + this.authentication.GetToken()
);
}
- return this.http.put(this.getUrl(url), params, {headers:headers});
+ return this.http.put(this.getUrl(url), params, {headers:headers})
+ .pipe(catchError(error => this.handleError(error)));
}
delete(url: string) {
@@ -67,7 +76,8 @@ export class ApiService {
'Bearer ' + this.authentication.GetToken()
);
}
- return this.http.delete(this.getUrl(url), {headers:headers});
+ return this.http.delete(this.getUrl(url), {headers:headers})
+ .pipe(catchError(error => this.handleError(error)));
}
getBlob(url: string) {
@@ -76,10 +86,20 @@ export class ApiService {
headers = headers.set('Authorization', 'Bearer ' + this.authentication.GetToken());
}
- return this.http.get(this.getUrl(url), { headers, responseType: 'blob' });
+ return this.http.get(this.getUrl(url), { headers, responseType: 'blob' })
+ .pipe(catchError(error => this.handleError(error)));
}
private getUrl(url: string): string {
return url.startsWith('/api/') ? url : `${this.baseUrl}${url}`;
}
+
+ private handleError(error: HttpErrorResponse) {
+ if (error.status === 401) {
+ this.authentication.LogOut();
+ void this.router.navigate(['/sign-in']);
+ }
+
+ return throwError(() => error);
+ }
}
diff --git a/ClientApp/src/index.html b/ClientApp/src/index.html
index 078363b..0d50024 100644
--- a/ClientApp/src/index.html
+++ b/ClientApp/src/index.html
@@ -10,9 +10,9 @@
-
+
-
\ No newline at end of file
+