diff --git a/.gitignore b/.gitignore index be59a7bc..46c77701 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ azure-pipelines.yml # dependencies /node_modules +package-lock.json # IDEs and editors /.idea @@ -48,4 +49,4 @@ gcp-service-key-prod.json gcp-api-key-prod.json reports/* -!reports/readme.md \ No newline at end of file +!reports/readme.md diff --git a/apps/migrate/src/app/app.service.ts b/apps/migrate/src/app/app.service.ts index bcd23036..3a62acfb 100644 --- a/apps/migrate/src/app/app.service.ts +++ b/apps/migrate/src/app/app.service.ts @@ -211,6 +211,7 @@ export class AppService { sendingTransactions: [], sendingVouchers: [], view: null, + tags: ep.tags }; }) .filter((u) => !!u) diff --git a/docker-compose.yml b/docker-compose.yml index 2d12316e..7424cc37 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -55,4 +55,3 @@ services: - .docker/firebase-emulators/.firebaserc:/.firebaserc command: > sh -c "firebase emulators:start --import=firebase-local-data --export-on-exit=firebase-local-data" - diff --git a/libs/client/ep/shell/src/lib/storefront/storefront.component.html b/libs/client/ep/shell/src/lib/storefront/storefront.component.html index f1701444..bd41aaec 100644 --- a/libs/client/ep/shell/src/lib/storefront/storefront.component.html +++ b/libs/client/ep/shell/src/lib/storefront/storefront.component.html @@ -48,6 +48,21 @@ + + + + {{ tag }} + + + + +
+
+ {{ tag }} + +
+
+
Storefront Images:
diff --git a/libs/client/ep/shell/src/lib/storefront/storefront.component.scss b/libs/client/ep/shell/src/lib/storefront/storefront.component.scss index 05ee9b69..28a28df0 100644 --- a/libs/client/ep/shell/src/lib/storefront/storefront.component.scss +++ b/libs/client/ep/shell/src/lib/storefront/storefront.component.scss @@ -15,3 +15,33 @@ color: black; } } + +.tags { + display:flex ; + gap: 10px ; +} +.tag { + background-color: var(--ion-color-primary) ; + padding: 4px 8px ; + border-radius: 8px ; + color:white ; + display:flex ; + align-items: center; + gap: 2px ; + +} + +.tags { + display:flex ; + gap: 10px ; +} +.tag { + background-color: var(--ion-color-primary) ; + padding: 4px 8px ; + border-radius: 8px ; + color:white ; + display:flex ; + align-items: center; + gap: 2px ; +} + diff --git a/libs/client/ep/shell/src/lib/storefront/storefront.component.ts b/libs/client/ep/shell/src/lib/storefront/storefront.component.ts index ecd75694..e83ba261 100644 --- a/libs/client/ep/shell/src/lib/storefront/storefront.component.ts +++ b/libs/client/ep/shell/src/lib/storefront/storefront.component.ts @@ -1,6 +1,8 @@ import { ChangeDetectionStrategy, Component, OnInit, ViewChild } from '@angular/core'; import { Validators } from '@angular/forms'; import { ActivatedRoute } from '@angular/router'; + + import { ImImagesViewerModalService, UserFacade, @@ -17,7 +19,9 @@ import { } from '@involvemint/shared/domain'; import { tapOnce, UnArray } from '@involvemint/shared/util'; import { FormControl, FormGroup } from '@ngneat/reactive-forms'; -import { filter, skip, switchMap, tap } from 'rxjs/operators'; +import { filter, skip, switchMap, tap, take } from 'rxjs/operators'; + +import { FormBuilder} from '@angular/forms'; type Profile = NonNullable['exchangePartner']>; @@ -36,12 +40,14 @@ interface State { }) export class StorefrontComponent extends StatefulComponent implements OnInit { @ViewChild('tabs') tabs!: ImTabsComponent; + availableTags: string[] = ['Essentials', 'Arts & Entertainment', 'Professional Services', 'Trades', 'Rentals', 'Farming/Land', 'Food & Food Services', 'Health & Wellness', 'Transportation', 'Home Services', 'Youth', 'Seniors', 'Education/Training', 'Retail', 'Media & Print', 'Free']; readonly storeFrontForm = new FormGroup({ listStoreFront: new FormControl(defaultProjectListingStatus, (e) => Validators.required(e) ), description: new FormControl('', [Validators.maxLength(ImConfig.maxDescriptionLength)]), + tags: new FormControl([]) }); readonly listingOptions: StorefrontListingStatus[] = ['public', 'private', 'unlisted']; @@ -63,7 +69,7 @@ export class StorefrontComponent extends StatefulComponent implements OnI if (!activeTab) { return; } - // re-route to same route to remove queryParams (activeTab) + this.route.to.ep.storefront.ROOT({ queryParams: { activeTab: undefined, @@ -76,10 +82,10 @@ export class StorefrontComponent extends StatefulComponent implements OnI activeTab === 'storefront' ? this.tabs.setIndex(0) : activeTab === 'offers' - ? this.tabs.setIndex(1) - : activeTab === 'requests' - ? this.tabs.setIndex(2) - : this.tabs.setIndex(0); + ? this.tabs.setIndex(1) + : activeTab === 'requests' + ? this.tabs.setIndex(2) + : this.tabs.setIndex(0); }); this.effect(() => @@ -92,6 +98,7 @@ export class StorefrontComponent extends StatefulComponent implements OnI this.storeFrontForm.patchValue({ listStoreFront: exchangePartner.listStoreFront, description: exchangePartner.description, + tags: exchangePartner.tags }); }), tap((exchangePartner) => { @@ -109,12 +116,37 @@ export class StorefrontComponent extends StatefulComponent implements OnI this.user.epProfile.dispatchers.editEpProfile({ listStoreFront: form.listStoreFront, description: form.description, + tags: form.tags, }); + }), + + tap((form) => { + + if (form.listStoreFront === 'private' || form.listStoreFront === 'unlisted') { + this.user.offers.selectors.offers$.pipe( + take(1), + tap(({ offers }) => { + offers.forEach(offer => { + this.user.offers.dispatchers.update({ + offerId: offer.id, + changes: { + listingStatus: form.listStoreFront, + name: offer.name, + description: offer.description, + price: offer.price + }, + }); + + }); + }) + ).subscribe(); + } }) ) ); } + tabChangeEvent(event: number) { if (typeof event !== 'number') { return; @@ -132,7 +164,7 @@ export class StorefrontComponent extends StatefulComponent implements OnI let files: File[] | undefined; try { files = parseMultipleFiles(event); - } catch (error) { + } catch (error:any) { this.status.presentAlert({ title: 'Error', description: error.message }); } @@ -145,6 +177,26 @@ export class StorefrontComponent extends StatefulComponent implements OnI this.user.epProfile.dispatchers.deleteEpImage(imagesFilePathsIndex); } + addTag(tag: string) { + console.log("we are hereeeeee") + const currentTags = this.storeFrontForm.get('tags')?.value || []; + if (tag && !currentTags.includes(tag)) { + this.storeFrontForm.get('tags')?.patchValue([...currentTags, tag]); + console.log('Updated Tags:', this.storeFrontForm.get('tags')?.value); + } + } + + onTagSelected(event: any) { + const selectedTag = event.detail.value; + this.addTag(selectedTag); + } + + removeTag(index: number): void { + const currentTags = [...this.storeFrontForm.get('tags')?.value]; // Make a copy of the array + currentTags.splice(index, 1); + this.storeFrontForm.get('tags')?.patchValue([...currentTags]); +} + makeCoverPhoto( ep: UnArray['exchangePartner'], imagesFilePathsIndex: number diff --git a/libs/client/login/src/lib/login-page/login-page.component.html b/libs/client/login/src/lib/login-page/login-page.component.html index 1036419b..56285e48 100644 --- a/libs/client/login/src/lib/login-page/login-page.component.html +++ b/libs/client/login/src/lib/login-page/login-page.component.html @@ -26,18 +26,20 @@ - + + + + + + Please enter your password. - + Log In @@ -49,4 +51,4 @@
- + \ No newline at end of file diff --git a/libs/client/login/src/lib/login-page/login-page.component.ts b/libs/client/login/src/lib/login-page/login-page.component.ts index d4355f47..762af787 100644 --- a/libs/client/login/src/lib/login-page/login-page.component.ts +++ b/libs/client/login/src/lib/login-page/login-page.component.ts @@ -17,6 +17,7 @@ interface State { changeDetection: ChangeDetectionStrategy.OnPush, }) export class LoginPageComponent extends StatefulComponent implements OnInit { + isPasswordVisible = false; readonly loginForm = new FormGroup({ email: new FormControl('', [(c) => Validators.required(c), Validators.pattern(ImConfig.regex.email)]), password: new FormControl('', (c) => Validators.required(c)), @@ -69,4 +70,8 @@ export class LoginPageComponent extends StatefulComponent implements OnIn async forgotPassword() { return this.route.to.forgotPassword.ROOT({ animation: 'back' }); } + + togglePasswordVisibility(): void { + this.isPasswordVisible = !this.isPasswordVisible; + } } diff --git a/libs/client/login/src/lib/sign-up/sign-up.component.html b/libs/client/login/src/lib/sign-up/sign-up.component.html index 99e6d53a..d37117cc 100644 --- a/libs/client/login/src/lib/sign-up/sign-up.component.html +++ b/libs/client/login/src/lib/sign-up/sign-up.component.html @@ -26,19 +26,28 @@ - + + + + + + + + {{ pwdValidationText }} - + + + + + + Your passwords do not match. @@ -51,12 +60,8 @@ - + Sign Up @@ -65,4 +70,4 @@
- + \ No newline at end of file diff --git a/libs/client/login/src/lib/sign-up/sign-up.component.ts b/libs/client/login/src/lib/sign-up/sign-up.component.ts index 3200bb96..808f38b6 100644 --- a/libs/client/login/src/lib/sign-up/sign-up.component.ts +++ b/libs/client/login/src/lib/sign-up/sign-up.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; +import { ChangeDetectionStrategy, Component, ContentChild, OnInit } from '@angular/core'; import { ValidationErrors, Validators } from '@angular/forms'; import { ActivatedRoute } from '@angular/router'; import { ImLegalModalInputs, ImLegalModalService, UserFacade } from '@involvemint/client/shared/data-access'; @@ -20,8 +20,11 @@ interface State { changeDetection: ChangeDetectionStrategy.OnPush, }) export class SignUpComponent extends StatefulComponent implements OnInit { + isPasswordVisible = false; + isConfirmPasswordVisible = false; readonly signUpForm = new FormGroup( { + email: new FormControl( '', Validators.compose([(c) => Validators.required(c), Validators.pattern(ImConfig.regex.email)]) @@ -48,6 +51,7 @@ export class SignUpComponent extends StatefulComponent implements OnInit private readonly uf: UserFacade, private readonly activatedRoute: ActivatedRoute, private readonly legalModal: ImLegalModalService + ) { super({ errorStatus: '' }); } @@ -98,7 +102,16 @@ export class SignUpComponent extends StatefulComponent implements OnInit this.updateState({ errorStatus: '' }); } + togglePasswordVisibility(): void { + this.isPasswordVisible = !this.isPasswordVisible; + } + + toggleConfirmPasswordVisibility(): void { + this.isConfirmPasswordVisible = !this.isConfirmPasswordVisible; + } // doGoogleLogin(): void { // this.uf.session.dispatchers.googleSignIn(environment.authPersistance); // } } + + diff --git a/libs/client/shared/data-access/src/lib/+state/session/user-session.effects.ts b/libs/client/shared/data-access/src/lib/+state/session/user-session.effects.ts index 75596837..459891e4 100644 --- a/libs/client/shared/data-access/src/lib/+state/session/user-session.effects.ts +++ b/libs/client/shared/data-access/src/lib/+state/session/user-session.effects.ts @@ -364,6 +364,27 @@ export class UserSessionEffects { ) ); + // readonly submitEpApplication$ = createEffect(() => + // this.actions$.pipe( + // ofType(UserSessionActions.submitEpApplication), + // delayWhen(() => from(this.status.showLoader('Submitting ExchangePartner Application...'))), + // pessimisticUpdate({ + // run: ({ dto }) => + // this.epApp.submit(UserQuery.epApplications, dto).pipe( + // map((epApp) => { + // // if (!ImConfig.requireApplicationApproval) { + // // window.location.reload(); + // // } + // this.status.dismissLoader(); + // this.infoModal.open({ + // title: 'ExchangePartner Application Successful', + // description: `Our administrators are reviewing your request and is pending approval or denial.`, + // icon: { name: 'checkmark', source: 'ionicon' }, + // useBackground: true, + // }); + // console.log("hit here debug 1"); + + @Injectable() readonly submitEpApplication$ = createEffect(() => this.actions$.pipe( ofType(UserSessionActions.submitEpApplication), @@ -371,18 +392,24 @@ export class UserSessionEffects { pessimisticUpdate({ run: ({ dto }) => this.epApp.submit(UserQuery.epApplications, dto).pipe( - map((epApp) => { - if (!ImConfig.requireApplicationApproval) { - window.location.reload(); - } + switchMap((epApp) => { this.status.dismissLoader(); - this.infoModal.open({ - title: 'ExchangePartner Application Successful', - description: `Our administrators are reviewing your request and is pending approval or denial.`, - icon: { name: 'checkmark', source: 'ionicon' }, - useBackground: true, - }); - return UserSessionActions.submitEpApplicationSuccess({ epApp }); + return from( + this.infoModal.open({ + title: 'ExchangePartner Application Successful', + description: `Our administrators are reviewing your request and is pending approval or denial.`, + icon: { name: 'checkmark', source: 'ionicon' }, + useBackground: true, + }) + ).pipe( + switchMap(modal => modal.onDidDismiss()), + map(result => { + if (result.data) { + // this.route.to.SomePageToGoTo.ROOT(); + } + return UserSessionActions.submitEpApplicationSuccess({ epApp }); + }) + ); }) ), onError: (action, { error }) => { diff --git a/libs/client/shared/ui/src/lib/im-amount-editor/im-amount-editor.component.html b/libs/client/shared/ui/src/lib/im-amount-editor/im-amount-editor.component.html index ecf65402..aa1fa16f 100644 --- a/libs/client/shared/ui/src/lib/im-amount-editor/im-amount-editor.component.html +++ b/libs/client/shared/ui/src/lib/im-amount-editor/im-amount-editor.component.html @@ -35,7 +35,7 @@
0 - < +
diff --git a/libs/client/shared/ui/src/lib/im-code-item/im-code-item.component.html b/libs/client/shared/ui/src/lib/im-code-item/im-code-item.component.html index 0041cf28..3a86046a 100644 --- a/libs/client/shared/ui/src/lib/im-code-item/im-code-item.component.html +++ b/libs/client/shared/ui/src/lib/im-code-item/im-code-item.component.html @@ -6,6 +6,6 @@
- {{ price / 100 | currency: '':'' }} + {{ price }}
diff --git a/libs/client/shared/util/src/index.ts b/libs/client/shared/util/src/index.ts index ff10e9a0..e5e57d29 100644 --- a/libs/client/shared/util/src/index.ts +++ b/libs/client/shared/util/src/index.ts @@ -1,6 +1,7 @@ export * from './lib/confirm-deactivation.guard'; export * from './lib/file.helpers'; export * from './lib/geolocation'; +export * from './lib/geolocation.service'; export * from './lib/info-modal/info-modal.module'; export * from './lib/modals.animations'; export * from './lib/popover/popover-ref'; diff --git a/libs/client/shared/util/src/lib/geolocation.service.ts b/libs/client/shared/util/src/lib/geolocation.service.ts new file mode 100644 index 00000000..75632e68 --- /dev/null +++ b/libs/client/shared/util/src/lib/geolocation.service.ts @@ -0,0 +1,64 @@ +import { Injectable } from '@angular/core'; +import { StatusService } from '@involvemint/client/shared/util'; +import { environment } from '@involvemint/shared/domain'; + +export interface LatLng2 { + lat: number; + lng: number; +} + +@Injectable({ + providedIn: 'root', +}) +export class GeolocationService { + constructor(private status: StatusService) {} + + getPosition(): Promise { + if (environment.environment === 'local') { + return new Promise((resolve) => { + resolve({ + lat: 40.444229, + lng: -79.943367, + }); + }); + } + + return new Promise(async (resolve, reject) => { + if (this.status) await this.status.showLoader('Getting Location...'); + navigator.geolocation.getCurrentPosition( + async (resp) => { + if (this.status) await this.status.dismissLoader(); + resolve({ lat: resp.coords.latitude, lng: resp.coords.longitude }); + }, + async (err) => { + if (this.status) await this.status.dismissLoader(); + console.warn('Could not get current location: ', err); + reject(err); + } + ); + }); + } + + parseLatLngAsString({ lat, lng }: LatLng2): string { + return `${lat},${lng}`; + } + + parseLatLngAsObj(latLng: string): LatLng2 { + const parse = latLng.split(','); + return { lat: Number(parse[0]), lng: Number(parse[1]) }; + } + + coordinateDistance( + lat1: number, + lon1: number, + lat2: number, + lon2: number, + unit: 'km' | 'miles' = 'miles' + ): number { + const p = 0.017453292519943295; // Math.PI / 180 + const c = Math.cos; + const a = 0.5 - c((lat2 - lat1) * p) / 2 + (c(lat1 * p) * c(lat2 * p) * (1 - c((lon2 - lon1) * p))) / 2; + + return 2 * (unit === 'km' ? 6371 : 3958.8) * Math.asin(Math.sqrt(a)); + } +} diff --git a/libs/client/shell/src/lib/applications/create-cm-profile/create-cm-profile.component.html b/libs/client/shell/src/lib/applications/create-cm-profile/create-cm-profile.component.html index d9979bb0..58af0b20 100644 --- a/libs/client/shell/src/lib/applications/create-cm-profile/create-cm-profile.component.html +++ b/libs/client/shell/src/lib/applications/create-cm-profile/create-cm-profile.component.html @@ -104,6 +104,29 @@ + +
+
+ +
Enable Location Services
+ + Please enable location services to use this platform. Your location helps us with mapping and + providing a better experience. + +
+ + +
+ Enable Location Services +
+
+
+ Back + Next +
+
+
+
diff --git a/libs/client/shell/src/lib/applications/create-cm-profile/create-cm-profile.component.scss b/libs/client/shell/src/lib/applications/create-cm-profile/create-cm-profile.component.scss index 106a9836..20e9804a 100644 --- a/libs/client/shell/src/lib/applications/create-cm-profile/create-cm-profile.component.scss +++ b/libs/client/shell/src/lib/applications/create-cm-profile/create-cm-profile.component.scss @@ -44,5 +44,12 @@ gap: var(--im-padding); padding: var(--im-padding); } + + .center-content { + display: flex; + justify-content: center; + align-items: center; + height: 100%; + } } } diff --git a/libs/client/shell/src/lib/applications/create-cm-profile/create-cm-profile.component.ts b/libs/client/shell/src/lib/applications/create-cm-profile/create-cm-profile.component.ts index d1ce164e..c1f14667 100644 --- a/libs/client/shell/src/lib/applications/create-cm-profile/create-cm-profile.component.ts +++ b/libs/client/shell/src/lib/applications/create-cm-profile/create-cm-profile.component.ts @@ -1,12 +1,8 @@ import { AfterViewInit, ChangeDetectionStrategy, Component, OnInit, ViewChild } from '@angular/core'; import { Validators } from '@angular/forms'; import { ActivatedRoute } from '@angular/router'; -import { - HandleRestClient, - UserFacade, - verifyHandleUniqueness, -} from '@involvemint/client/shared/data-access'; -import { StatefulComponent } from '@involvemint/client/shared/util'; +import { HandleRestClient, UserFacade, verifyHandleUniqueness } from '@involvemint/client/shared/data-access'; +import { StatefulComponent, GeolocationService } from '@involvemint/client/shared/util'; import { CmOnboardingState, ImConfig } from '@involvemint/shared/domain'; import { DeepReadonly, STATES } from '@involvemint/shared/util'; import { IonSlides } from '@ionic/angular'; @@ -26,6 +22,7 @@ export interface CreateCmProfileFormData { interface State { verifyingHandle: boolean; + locationEnabled: boolean; } @Component({ @@ -58,9 +55,10 @@ export class CreateCmProfileComponent extends StatefulComponent implement constructor( private readonly uf: UserFacade, private readonly handleRestClient: HandleRestClient, - private readonly route: ActivatedRoute + private readonly route: ActivatedRoute, + private readonly geolocationService: GeolocationService ) { - super({ verifyingHandle: false }); + super({ verifyingHandle: false, locationEnabled: false }); } ngOnInit(): void { @@ -93,6 +91,23 @@ export class CreateCmProfileComponent extends StatefulComponent implement this.slides.lockSwipes(true); } + enableLocation() { + this.geolocationService + .getPosition() + .then((position) => { + console.log('Location retrieved:', position); + // store somewhere, tbd, TODO + this.updateState({ locationEnabled: true }); + // this.next(); + }) + .catch((error) => { + console.error('Error retrieving location:', error); + if (error.code === error.PERMISSION_DENIED) { + alert('Please enable location services to use this feature.'); + } + }); + } + submit(): void { this.uf.session.dispatchers.createCmProfile({ ...this.createProfileForm.value, diff --git a/libs/client/shell/src/lib/applications/ep-application/ep-application.component.html b/libs/client/shell/src/lib/applications/ep-application/ep-application.component.html index 69e7b0ad..7b7b541e 100644 --- a/libs/client/shell/src/lib/applications/ep-application/ep-application.component.html +++ b/libs/client/shell/src/lib/applications/ep-application/ep-application.component.html @@ -190,7 +190,18 @@
- + + Enable Location Services + + {{ baAdmin ? (applyFor === 'business' ? 'Submit for Business' : 'Submit for Yourself') : 'Submit' }}
diff --git a/libs/client/shell/src/lib/applications/ep-application/ep-application.component.ts b/libs/client/shell/src/lib/applications/ep-application/ep-application.component.ts index 7147ee43..2f44706e 100644 --- a/libs/client/shell/src/lib/applications/ep-application/ep-application.component.ts +++ b/libs/client/shell/src/lib/applications/ep-application/ep-application.component.ts @@ -7,7 +7,11 @@ import { verifyHandleUniqueness, verifyUserEmailUniqueness, } from '@involvemint/client/shared/data-access'; -import { ConfirmDeactivationGuard, StatefulComponent } from '@involvemint/client/shared/util'; +import { + ConfirmDeactivationGuard, + StatefulComponent, + GeolocationService, +} from '@involvemint/client/shared/util'; import { ImConfig, SubmitEpApplicationDto } from '@involvemint/shared/domain'; import { STATES } from '@involvemint/shared/util'; import { FormControl, FormGroup } from '@ngneat/reactive-forms'; @@ -20,6 +24,7 @@ interface EpForm extends Omit { interface State { verifyingUserEmail: boolean; verifyingHandle: boolean; + locationEnabled: boolean; } @Component({ @@ -59,9 +64,10 @@ export class EpApplicationComponent constructor( private readonly user: UserFacade, private readonly userClient: UserRestClient, - private readonly handleRestClient: HandleRestClient + private readonly handleRestClient: HandleRestClient, + private readonly geolocationService: GeolocationService ) { - super({ verifyingUserEmail: false, verifyingHandle: false }); + super({ verifyingUserEmail: false, verifyingHandle: false, locationEnabled: false }); } @HostListener('window:beforeunload', ['$event']) @@ -110,4 +116,19 @@ export class EpApplicationComponent this.applyFor = 'yourself'; } } + + enableLocation() { + this.geolocationService + .getPosition() + .then((position) => { + console.log('Location retrieved:', position); + this.updateState({ locationEnabled: true }); + }) + .catch((error) => { + console.error('Error retrieving location:', error); + if (error.code === error.PERMISSION_DENIED) { + alert('Please enable location services to use this feature.'); + } + }); + } } diff --git a/libs/client/shell/src/lib/applications/sp-application/sp-application.component.html b/libs/client/shell/src/lib/applications/sp-application/sp-application.component.html index 18244d65..724ae7b5 100644 --- a/libs/client/shell/src/lib/applications/sp-application/sp-application.component.html +++ b/libs/client/shell/src/lib/applications/sp-application/sp-application.component.html @@ -132,7 +132,18 @@
- + + Enable Location Services + + Submit
diff --git a/libs/client/shell/src/lib/applications/sp-application/sp-application.component.ts b/libs/client/shell/src/lib/applications/sp-application/sp-application.component.ts index 3a1f595c..a2485b86 100644 --- a/libs/client/shell/src/lib/applications/sp-application/sp-application.component.ts +++ b/libs/client/shell/src/lib/applications/sp-application/sp-application.component.ts @@ -1,11 +1,11 @@ import { ChangeDetectionStrategy, Component, HostListener, OnInit } from '@angular/core'; import { Validators } from '@angular/forms'; +import { HandleRestClient, UserFacade, verifyHandleUniqueness } from '@involvemint/client/shared/data-access'; import { - HandleRestClient, - UserFacade, - verifyHandleUniqueness, -} from '@involvemint/client/shared/data-access'; -import { ConfirmDeactivationGuard, StatefulComponent } from '@involvemint/client/shared/util'; + ConfirmDeactivationGuard, + StatefulComponent, + GeolocationService, +} from '@involvemint/client/shared/util'; import { ImConfig, SubmitSpApplicationDto } from '@involvemint/shared/domain'; import { STATES } from '@involvemint/shared/util'; import { FormControl, FormGroup } from '@ngneat/reactive-forms'; @@ -16,6 +16,8 @@ interface SpForm extends Omit { } interface State { + locationEnabled: boolean; + verifyingHandle: boolean; } @@ -27,7 +29,8 @@ interface State { }) export class SpApplicationComponent extends StatefulComponent - implements OnInit, ConfirmDeactivationGuard { + implements OnInit, ConfirmDeactivationGuard +{ readonly spForm = new FormGroup({ address1: new FormControl('', [(c) => Validators.required(c)]), address2: new FormControl(''), @@ -45,8 +48,12 @@ export class SpApplicationComponent readonly USStates = STATES; - constructor(private readonly user: UserFacade, private readonly handleRestClient: HandleRestClient) { - super({ verifyingHandle: false }); + constructor( + private readonly user: UserFacade, + private readonly handleRestClient: HandleRestClient, + private readonly geolocationService: GeolocationService + ) { + super({ verifyingHandle: false, locationEnabled: false }); } @HostListener('window:beforeunload', ['$event']) @@ -70,4 +77,19 @@ export class SpApplicationComponent this.spForm.markAsPristine(); this.user.session.dispatchers.submitSpApplication(this.spForm.value); } + + enableLocation() { + this.geolocationService + .getPosition() + .then((position) => { + console.log('Location retrieved:', position); + this.updateState({ locationEnabled: true }); + }) + .catch((error) => { + console.error('Error retrieving location:', error); + if (error.code === error.PERMISSION_DENIED) { + alert('Please enable location services to use this feature.'); + } + }); + } } diff --git a/libs/client/shell/src/lib/im-welcome-modal/im-welcome-modal.component.html b/libs/client/shell/src/lib/im-welcome-modal/im-welcome-modal.component.html index 0ea668e0..f254f735 100644 --- a/libs/client/shell/src/lib/im-welcome-modal/im-welcome-modal.component.html +++ b/libs/client/shell/src/lib/im-welcome-modal/im-welcome-modal.component.html @@ -1,9 +1,9 @@ - + Welcome diff --git a/libs/client/shell/src/lib/im-welcome-modal/im-welcome-modal.service.ts b/libs/client/shell/src/lib/im-welcome-modal/im-welcome-modal.service.ts index 5595e63d..608703b7 100644 --- a/libs/client/shell/src/lib/im-welcome-modal/im-welcome-modal.service.ts +++ b/libs/client/shell/src/lib/im-welcome-modal/im-welcome-modal.service.ts @@ -7,7 +7,10 @@ export class ImWelcomeModalService { constructor(private readonly modal: ModalController) {} async open() { - const modal = await this.modal.create({ component: ImWelcomeModalComponent }); + const modal = await this.modal.create({ + component: ImWelcomeModalComponent, + backdropDismiss: false + }); await modal.present(); return modal.onDidDismiss(); } diff --git a/libs/client/shell/src/lib/market/ep-cover/ep-cover.component.html b/libs/client/shell/src/lib/market/ep-cover/ep-cover.component.html index b7ee9412..16549b12 100644 --- a/libs/client/shell/src/lib/market/ep-cover/ep-cover.component.html +++ b/libs/client/shell/src/lib/market/ep-cover/ep-cover.component.html @@ -8,7 +8,7 @@ - Posted by: + Seller Info:
diff --git a/libs/client/shell/src/lib/market/market.component.scss b/libs/client/shell/src/lib/market/market.component.scss index 8baec287..c0f98427 100644 --- a/libs/client/shell/src/lib/market/market.component.scss +++ b/libs/client/shell/src/lib/market/market.component.scss @@ -151,3 +151,34 @@ img { border-radius: 13px; } } + +.tags { + display:flex ; + gap: 4px ; + flex-wrap: wrap; +} + +.tag { + background-color: var(--ion-color-primary) ; + color: white ; + font-size: 10px ; + padding: 2px 8px ; + border-radius: 4px; + white-space: nowrap; +} + + +.tags { +display:flex ; +gap: 4px ; +flex-wrap: wrap; +} + +.tag { +background-color: var(--ion-color-primary) ; +color: white ; +font-size: 10px ; +padding: 2px 8px ; +border-radius: 4px; +white-space: nowrap; +} \ No newline at end of file diff --git a/libs/client/shell/src/lib/projects/browse-projects/browse-projects.component.ts b/libs/client/shell/src/lib/projects/browse-projects/browse-projects.component.ts index de7b9f34..bb7e4947 100644 --- a/libs/client/shell/src/lib/projects/browse-projects/browse-projects.component.ts +++ b/libs/client/shell/src/lib/projects/browse-projects/browse-projects.component.ts @@ -6,7 +6,7 @@ import { } from '@involvemint/client/shared/data-access'; import { RouteService } from '@involvemint/client/shared/routes'; import { StatefulComponent } from '@involvemint/client/shared/util'; -import { calculateEnrollmentStatus, EnrollmentStatus } from '@involvemint/shared/domain'; +import { calculateEnrollmentStatus, calculateProjectExpiry, EnrollmentStatus, ProjectStatus, ProjectListingStatus } from '@involvemint/shared/domain'; import { parseDate } from '@involvemint/shared/util'; import { formatDistanceToNow } from 'date-fns'; import { tap } from 'rxjs/operators'; @@ -22,9 +22,11 @@ interface State { styleUrls: ['./browse-projects.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, }) + export class BrowseProjectsComponent extends StatefulComponent implements OnInit { @Input() inline = false; + constructor( private readonly user: UserFacade, private readonly route: RouteService, @@ -36,18 +38,27 @@ export class BrowseProjectsComponent extends StatefulComponent implements ngOnInit() { this.effect(() => this.user.projects.selectors.projects$.pipe( - tap(({ projects, loaded }) => + tap(({ projects, loaded }) => { + const updatedProjects = projects.map((project) => { + const status = calculateProjectExpiry(project); + if (status === ProjectStatus.open) { + return { ...project, listingStatus: 'public' as ProjectListingStatus }; + } + else { + return { ...project, listingStatus: 'private' as ProjectListingStatus }; + } + return project; + }); this.updateState({ - // Filter by public so it won't show any unlisted projects that an SP may have viewed - // then went back to the projects feed. - projects: projects.filter((p) => p.listingStatus === 'public'), + projects: updatedProjects.filter((p) => p.listingStatus === 'public'), loaded, - }) - ) + }); + }) ) ); } + refresh() { this.user.projects.dispatchers.refresh(); } diff --git a/libs/client/shell/src/lib/projects/project-cover-page/project-cover-page.component.html b/libs/client/shell/src/lib/projects/project-cover-page/project-cover-page.component.html index b9773abd..14670f94 100644 --- a/libs/client/shell/src/lib/projects/project-cover-page/project-cover-page.component.html +++ b/libs/client/shell/src/lib/projects/project-cover-page/project-cover-page.component.html @@ -22,7 +22,16 @@
-
{{ project.title }}
+
+ {{ project.title }} + + {{ state.projectState === ProjectStatus.open ? 'Join' : 'Closed' }} + +
{{ project.servePartner.name }}
diff --git a/libs/client/shell/src/lib/projects/project-cover-page/project-cover-page.component.scss b/libs/client/shell/src/lib/projects/project-cover-page/project-cover-page.component.scss index 192ec2f0..674b6314 100644 --- a/libs/client/shell/src/lib/projects/project-cover-page/project-cover-page.component.scss +++ b/libs/client/shell/src/lib/projects/project-cover-page/project-cover-page.component.scss @@ -17,3 +17,13 @@ display: grid; gap: 0.25rem; } + +.im-primary-text { + display: flex; + justify-content: space-between; + align-items: center; +} + +.im-primary-text ion-button { + margin-left: auto; +} diff --git a/libs/client/shell/src/lib/wallet/wallet.component.html b/libs/client/shell/src/lib/wallet/wallet.component.html index 5b115c51..3feee7e6 100644 --- a/libs/client/shell/src/lib/wallet/wallet.component.html +++ b/libs/client/shell/src/lib/wallet/wallet.component.html @@ -65,13 +65,11 @@

No recent transactions.

- - @{{ - state.activeProfile?.handle?.id === t.meta.senderHandle - ? t.meta.receiverHandle - : t.meta.senderHandle - }} - + + {{ t.epAudibleCode }} {{ t.dateTransacted | date: 'M/dd/yy' }}
@@ -106,11 +104,16 @@
- @{{ - state.activeProfile?.handle?.id === t.meta.senderHandle + + +
{{ t.epAudibleCode }} @@ -164,7 +167,7 @@ Balance Leftover: - {{ state.balance - amount.value | currency: '':'' }} + {{ state.balance - amount.value > 0 ? (state.balance - amount.value | currency: '':'') : '0' }}
- +
- + Valued at: {{ amount.value | currency }} USD
diff --git a/libs/client/shell/src/lib/wallet/wallet.component.ts b/libs/client/shell/src/lib/wallet/wallet.component.ts index 65e9a873..21ed4f27 100644 --- a/libs/client/shell/src/lib/wallet/wallet.component.ts +++ b/libs/client/shell/src/lib/wallet/wallet.component.ts @@ -95,7 +95,7 @@ export class WalletComponent extends StatefulComponent implements OnInit creditsLoaded: loaded, balance: balance / 100, escrowCredits, - escrowBalance, + escrowBalance: escrowBalance / 100 }); }) ) @@ -229,3 +229,6 @@ export class WalletComponent extends StatefulComponent implements OnInit return run; } } + + + diff --git a/libs/client/sp/shell/src/lib/projects/projects-page/projects-page.component.ts b/libs/client/sp/shell/src/lib/projects/projects-page/projects-page.component.ts index 172ee5dd..96aa4043 100644 --- a/libs/client/sp/shell/src/lib/projects/projects-page/projects-page.component.ts +++ b/libs/client/sp/shell/src/lib/projects/projects-page/projects-page.component.ts @@ -2,6 +2,7 @@ import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; import { RouteService } from '@involvemint/client/shared/routes'; import { StatefulComponent } from '@involvemint/client/shared/util'; import { ProjectSpStoreModel, ServePartnerFacade } from '@involvemint/client/sp/data-access'; +import { calculateProjectExpiry, ProjectStatus, ProjectListingStatus } from '@involvemint/shared/domain'; import { tap } from 'rxjs/operators'; interface State { @@ -24,12 +25,26 @@ export class ProjectsPageComponent extends StatefulComponent implements O this.effect(() => this.sp.projects.selectors.projects$.pipe( tap(({ projects, loaded }) => { - this.updateState({ projects, loaded }); + const updatedProjects = projects.map((project) => { + const status = calculateProjectExpiry(project); + if (status === ProjectStatus.open) { + return { ...project, listingStatus: 'public' as ProjectListingStatus }; + } + else { + return { ...project, listingStatus: 'private' as ProjectListingStatus }; + } + return project; + }); + this.updateState({ + projects: updatedProjects.filter((p) => p.listingStatus === 'public' || p.listingStatus === 'private'), + loaded, + }); }) ) ); } + refresh() { this.sp.projects.dispatchers.refresh(); } diff --git a/libs/server/core/application-services/src/lib/ep-application/ep-application.service.ts b/libs/server/core/application-services/src/lib/ep-application/ep-application.service.ts index c857399f..d1085079 100644 --- a/libs/server/core/application-services/src/lib/ep-application/ep-application.service.ts +++ b/libs/server/core/application-services/src/lib/ep-application/ep-application.service.ts @@ -235,6 +235,7 @@ export class EpApplicationService { receivingVouchers: [], sendingVouchers: [], view: null, + tags: [] }); } }); diff --git a/libs/server/core/domain-services/src/lib/exchange-partner/exchange-partner.entity.ts b/libs/server/core/domain-services/src/lib/exchange-partner/exchange-partner.entity.ts index 13a0ec23..23fb7993 100644 --- a/libs/server/core/domain-services/src/lib/exchange-partner/exchange-partner.entity.ts +++ b/libs/server/core/domain-services/src/lib/exchange-partner/exchange-partner.entity.ts @@ -51,6 +51,9 @@ export class ExchangePartnerEntity implements Required { @Column('text', { default: EpOnboardingState.profile }) onboardingState!: EpOnboardingState; + @Column('simple-array', { default: '' }) + tags: string[] = []; + @OneToOne(() => HandleEntity, (e) => e.exchangePartner, { cascade: true }) @JoinColumn() handle!: HandleEntity; diff --git a/libs/shared/domain/src/lib/config/im-config.ts b/libs/shared/domain/src/lib/config/im-config.ts index a3cb7c93..2a8a5637 100644 --- a/libs/shared/domain/src/lib/config/im-config.ts +++ b/libs/shared/domain/src/lib/config/im-config.ts @@ -31,7 +31,7 @@ export const ImConfig = convertDeepReadonly({ lastName: /^[a-zA-Z ]+$/, // can contain multiple names handle: /^([\-\w]){4,25}$/, // minimum 4 characters bio: /^.{0,100}$/, // can be empty but max of 100 characters - email: /(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/, + email: /^(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])$/i, phone: /^(\([2-9]\d{2}\))\s\d{3}-\d{4}$/, url: /^(http:\/\/www\.|https:\/\/www\.|http:\/\/|https:\/\/)?[a-zA-Z0-9]+([\-\.]{1}[a-zA-Z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)?$/, ein: /^([07][1-7]|1[0-6]|2[0-7]|[35][0-9]|[468][0-8]|9[0-589])-?\d{7}$/, diff --git a/libs/shared/domain/src/lib/domain/exchange-admin/exchange-admin.queries.ts b/libs/shared/domain/src/lib/domain/exchange-admin/exchange-admin.queries.ts index 5e3b950f..bdea3566 100644 --- a/libs/shared/domain/src/lib/domain/exchange-admin/exchange-admin.queries.ts +++ b/libs/shared/domain/src/lib/domain/exchange-admin/exchange-admin.queries.ts @@ -42,5 +42,6 @@ export const BaDownloadEpAdminsQuery = createQuery()({ view: { receivedThisMonth: true, }, + tags:true }, }); diff --git a/libs/shared/domain/src/lib/domain/exchange-partner/exchange-partner.model.ts b/libs/shared/domain/src/lib/domain/exchange-partner/exchange-partner.model.ts index b6c2738a..974d3db3 100644 --- a/libs/shared/domain/src/lib/domain/exchange-partner/exchange-partner.model.ts +++ b/libs/shared/domain/src/lib/domain/exchange-partner/exchange-partner.model.ts @@ -34,6 +34,7 @@ export interface ExchangePartner { longitude?: number; dateCreated: Date | string; onboardingState: EpOnboardingState; + tags: string[] ; address: IOneToOne; handle: IOneToOne; diff --git a/libs/shared/domain/src/lib/domain/exchange-partner/exchange-partner.queries.ts b/libs/shared/domain/src/lib/domain/exchange-partner/exchange-partner.queries.ts index 7f1c728e..ab573002 100644 --- a/libs/shared/domain/src/lib/domain/exchange-partner/exchange-partner.queries.ts +++ b/libs/shared/domain/src/lib/domain/exchange-partner/exchange-partner.queries.ts @@ -63,6 +63,7 @@ export const ExchangePartnerMarketQuery = createQuery()({ dateUpdated: true, imagesFilePaths: true, }, + tags:true }); export const BaDownloadEpIdsWithEmailQuery = createQuery()({ diff --git a/libs/shared/domain/src/lib/domain/project/project.logic.ts b/libs/shared/domain/src/lib/domain/project/project.logic.ts index 72d08ec0..06cb8bf4 100644 --- a/libs/shared/domain/src/lib/domain/project/project.logic.ts +++ b/libs/shared/domain/src/lib/domain/project/project.logic.ts @@ -10,6 +10,22 @@ export enum ProjectStatus { closed = 'closed', } +export const calculateProjectExpiry = createLogic< + Project, + { + startDate: true; + endDate: true; + } +>()((project) => { + const currentDate = new Date(); + if ( + (project.endDate && isAfter(currentDate, parseDate(project.endDate))) + ) { + return ProjectStatus.closed; + } + return ProjectStatus.open; +}); + export const calculateProjectStatus = createLogic< Project, { diff --git a/libs/shared/domain/src/lib/domain/user/user.queries.ts b/libs/shared/domain/src/lib/domain/user/user.queries.ts index 718d0463..73a52166 100644 --- a/libs/shared/domain/src/lib/domain/user/user.queries.ts +++ b/libs/shared/domain/src/lib/domain/user/user.queries.ts @@ -68,6 +68,7 @@ export const UserQuery = createQuery()({ view: { receivedThisMonth: true, }, + tags:true }, }, serveAdmins: {