-
Notifications
You must be signed in to change notification settings - Fork 4
Feat/auth and save story #25
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
base: main
Are you sure you want to change the base?
Changes from 9 commits
4f6fb77
ad4c86b
a5c536d
afb31da
50262f4
103a9e0
130f103
8fc080e
f8fcf0a
e581c3c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,68 @@ | ||
| <section class="py-12 px-6 bg-base-100"> | ||
| <h2 class="text-center text-3xl font-bold mb-10">Your Stories</h2> | ||
|
|
||
| <div class="max-w-3xl mx-auto flex flex-col gap-6"> | ||
| @if (!loading() && userStories().length > 0) { @for (story of userStories(); | ||
| track story.id) { | ||
| <div class="card bg-base-200 shadow-xl min-h-48"> | ||
| <figure class="relative aspect-video overflow-hidden"> | ||
| <img | ||
| [src]="story.storyParts[0].imageUri" | ||
| alt="{{ story.name }}" | ||
| class="absolute inset-0 w-full h-full object-cover" | ||
| /> | ||
| </figure> | ||
| <div class="card-body text-center items-center"> | ||
| <h3 class="card-title text-lg h-16 text-white">{{ story.name }}</h3> | ||
| <div class="card-actions justify-center gap-2"> | ||
| <a | ||
| class="btn btn-primary" | ||
| [routerLink]="'/viewStory'" | ||
| [queryParams]="{ id: story.id }" | ||
| > | ||
| Read Story | ||
| </a> | ||
| <button | ||
| class="btn btn-secondary" | ||
| (click)="openConfirmationModal(story.id)" | ||
| > | ||
| Unsave | ||
| </button> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| } } @else if (!loading()) { | ||
| <p class="text-center text-gray-400">No stories saved yet.</p> | ||
| } @else { | ||
| <!-- Skeleton loader --> | ||
| @for (_ of [1, 2]; track _) { | ||
| <div class="card bg-base-200 shadow-xl min-h-48 animate-pulse"> | ||
| <figure class="h-48 bg-base-300"> | ||
| <div class="skeleton w-full h-full"></div> | ||
| </figure> | ||
| <div class="card-body text-center items-center space-y-4"> | ||
| <div class="skeleton h-6 w-3/4"></div> | ||
| <div class="skeleton h-10 w-24"></div> | ||
| </div> | ||
| </div> | ||
| } } | ||
| </div> | ||
| </section> | ||
|
|
||
| <!-- Confirmation Modal --> | ||
| @if (isModalOpen()) { | ||
| <dialog class="modal modal-open modal-bottom sm:modal-middle"> | ||
| <div class="modal-box"> | ||
| <h3 class="font-bold text-lg">Confirm Unsave</h3> | ||
| <p class="py-4"> | ||
| Are you sure you want to unsave this story? This action cannot be undone. | ||
| </p> | ||
| <div class="modal-action"> | ||
| <button class="btn btn-ghost" (click)="closeConfirmationModal()"> | ||
| Cancel | ||
| </button> | ||
| <button class="btn btn-error" (click)="confirmUnsave()">Unsave</button> | ||
| </div> | ||
| </div> | ||
| </dialog> | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| import { Component, inject, effect, signal } from '@angular/core'; | ||
| import { StorySaveService } from '../../services/save.story.service'; | ||
| import { AuthStore } from '../../services/auth.store'; | ||
| import { CommonModule } from '@angular/common'; | ||
| import { RouterLink } from '@angular/router'; | ||
|
|
||
| @Component({ | ||
| selector: 'app-my-stories', | ||
| templateUrl: './my-stories.html', | ||
| styleUrls: ['./my-stories.css'], | ||
| imports: [CommonModule, RouterLink], | ||
| }) | ||
| export class UserStoriesComponent { | ||
| userStories = signal<any[]>([]); | ||
| loading = signal(true); | ||
| isModalOpen = signal(false); | ||
| selectedStoryId = signal<string | null>(null); | ||
|
|
||
| private readonly storyService = inject(StorySaveService); | ||
| private readonly authService = inject(AuthStore); | ||
|
|
||
| constructor() { | ||
| // reactively respond to auth changes | ||
| effect(async () => { | ||
| const user = this.authService.user(); // signal | ||
| if (user?.uid) { | ||
| this.loading.set(true); | ||
| try { | ||
| const stories = await this.storyService.getUserStory(user.uid); | ||
|
|
||
| const storiesArray = stories.map((v) => ({ ...v.data(), id: v.id })); | ||
| this.userStories.set(storiesArray); | ||
| console.log(this.userStories()); | ||
| this.loading.set(false); | ||
| } catch (err) { | ||
| console.error('Error loading stories:', err); | ||
| } finally { | ||
| this.loading.set(false); | ||
|
Comment on lines
+36
to
+37
|
||
| } | ||
| } | ||
| }); | ||
| } | ||
|
|
||
| openConfirmationModal(storyId: string): void { | ||
| this.selectedStoryId.set(storyId); | ||
| this.isModalOpen.set(true); | ||
| } | ||
|
|
||
| closeConfirmationModal(): void { | ||
| this.isModalOpen.set(false); | ||
| this.selectedStoryId.set(null); | ||
| } | ||
|
|
||
| async confirmUnsave(): Promise<void> { | ||
| const storyId = this.selectedStoryId(); | ||
| const user = this.authService.user(); | ||
|
|
||
| if (storyId && user?.uid) { | ||
| const result = await this.storyService.removeSavedStory( | ||
| storyId, | ||
| user.uid | ||
| ); | ||
| if (result.success) { | ||
| this.userStories.update((stories) => | ||
| stories.filter((story) => story.id !== storyId) | ||
| ); | ||
| } else { | ||
| // Optional: handle error with a user-facing message | ||
| console.error('Failed to unsave story:', result.message); | ||
| } | ||
| this.closeConfirmationModal(); | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,61 @@ | ||||||
| <div | ||||||
| class="min-h-screen flex items-center justify-center bg-dracula text-dracula-foreground" | ||||||
| > | ||||||
| <div class="w-full max-w-md p-8 rounded-2xl shadow-lg bg-dracula-selection"> | ||||||
| <!-- Branding --> | ||||||
| <div class="text-center mb-6"> | ||||||
| <h1 class="text-3xl font-bold text-primary">Kidlytics</h1> | ||||||
| <p class="text-sm text-dracula-comment mt-1">AI Powered Story Teller</p> | ||||||
| </div> | ||||||
|
|
||||||
| <!-- Login Form --> | ||||||
| <form | ||||||
| [formGroup]="form" | ||||||
| (ngSubmit)="onEmailLogin()" | ||||||
| class="flex flex-col gap-4" | ||||||
| > | ||||||
| <input | ||||||
| class="p-3 rounded-lg border border-dracula-purple bg-dracula text-dracula-foreground focus:ring-2 focus:ring-primary transition" | ||||||
| type="email" | ||||||
| formControlName="email" | ||||||
| placeholder="Email" | ||||||
| /> | ||||||
|
|
||||||
| <input | ||||||
| class="p-3 rounded-lg border border-dracula-purple bg-dracula text-dracula-foreground focus:ring-2 focus:ring-primary transition" | ||||||
| type="password" | ||||||
| formControlName="password" | ||||||
| placeholder="Password" | ||||||
| /> | ||||||
|
|
||||||
| <button | ||||||
| type="submit" | ||||||
| class="btn-primary p-3 rounded-lg text-white font-bold transition hover:scale-105 disabled:opacity-50 cursor-pointer border b-5 shadow-lg" | ||||||
|
||||||
| class="btn-primary p-3 rounded-lg text-white font-bold transition hover:scale-105 disabled:opacity-50 cursor-pointer border b-5 shadow-lg" | |
| class="btn-primary p-3 rounded-lg text-white font-bold transition hover:scale-105 disabled:opacity-50 cursor-pointer border border-5 shadow-lg" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Replace 'any[]' with a proper type definition. Consider creating an interface for the story data structure to improve type safety.