diff --git a/.prettierrc b/.prettierrc index 057e899..bc1039f 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,5 +1,6 @@ { "arrowParens": "avoid", + "printWidth": 120, "semi": true, "singleQuote": true, "tabWidth": 4, diff --git a/angular.json b/angular.json index 8e5b70a..3cbc757 100644 --- a/angular.json +++ b/angular.json @@ -25,7 +25,7 @@ "input": "public" } ], - "styles": ["src/styles.css"] + "styles": ["src/material-theme.scss", "src/styles.css"] }, "configurations": { "production": { diff --git a/package-lock.json b/package-lock.json index 52ae2c3..1e61f7d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,10 +8,12 @@ "name": "meals-made-easy-app", "version": "0.0.0", "dependencies": { + "@angular/cdk": "^21.0.6", "@angular/common": "^21.0.0", "@angular/compiler": "^21.0.0", "@angular/core": "^21.0.0", "@angular/forms": "^21.0.0", + "@angular/material": "^21.0.6", "@angular/platform-browser": "^21.0.0", "@angular/router": "^21.0.0", "@fortawesome/angular-fontawesome": "^4.0.0", @@ -424,6 +426,22 @@ } } }, + "node_modules/@angular/cdk": { + "version": "21.0.6", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-21.0.6.tgz", + "integrity": "sha512-5Gw8mXtKXvcvDMWEciPLRYB6Ja5vsikLAidZsdCEIF6Bc51GmoqT5Tk/Ke+ciCd5Hq9Aco/IcHxT1RC3470lZg==", + "license": "MIT", + "peer": true, + "dependencies": { + "parse5": "^8.0.0", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/common": "^21.0.0 || ^22.0.0", + "@angular/core": "^21.0.0 || ^22.0.0", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, "node_modules/@angular/cli": { "version": "21.0.2", "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-21.0.2.tgz", @@ -555,6 +573,7 @@ "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-21.0.3.tgz", "integrity": "sha512-W60auwyDmsglIlHAbP/eol0LyzQ6FCz8LHghNx2B4RjIpuIMyjBLBZfC0JHU0gyiKB/JfX8W4FdphvyT7I4sIw==", "license": "MIT", + "peer": true, "dependencies": { "tslib": "^2.3.0" }, @@ -569,6 +588,23 @@ "rxjs": "^6.5.3 || ^7.4.0" } }, + "node_modules/@angular/material": { + "version": "21.0.6", + "resolved": "https://registry.npmjs.org/@angular/material/-/material-21.0.6.tgz", + "integrity": "sha512-BSbqFkVIjpXS+UGD7R1jDnuKArMCtLSKHL/1f/9mvHM4AntRFC88MQJMjS0k+pHCofN+MBMEpzBVrDOOqL+46A==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/cdk": "21.0.6", + "@angular/common": "^21.0.0 || ^22.0.0", + "@angular/core": "^21.0.0 || ^22.0.0", + "@angular/forms": "^21.0.0 || ^22.0.0", + "@angular/platform-browser": "^21.0.0 || ^22.0.0", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, "node_modules/@angular/platform-browser": { "version": "21.0.3", "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-21.0.3.tgz", @@ -7157,7 +7193,6 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.0.tgz", "integrity": "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==", - "dev": true, "license": "MIT", "dependencies": { "entities": "^6.0.0" @@ -7211,7 +7246,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", - "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=0.12" diff --git a/package.json b/package.json index 229af2e..a9aa721 100644 --- a/package.json +++ b/package.json @@ -9,8 +9,7 @@ "test": "ng test" }, "prettier": { - "printWidth": 100, - "singleQuote": true, + "printWidth": 120, "overrides": [ { "files": "*.html", @@ -23,10 +22,12 @@ "private": true, "packageManager": "npm@11.6.2", "dependencies": { + "@angular/cdk": "^21.0.6", "@angular/common": "^21.0.0", "@angular/compiler": "^21.0.0", "@angular/core": "^21.0.0", "@angular/forms": "^21.0.0", + "@angular/material": "^21.0.6", "@angular/platform-browser": "^21.0.0", "@angular/router": "^21.0.0", "@fortawesome/angular-fontawesome": "^4.0.0", diff --git a/src/app/pages/recipe-page/recipe-page-content/recipe-page-content.css b/src/app/pages/recipe-page/recipe-page-content/recipe-page-content.css index 365d0a2..840d0a5 100644 --- a/src/app/pages/recipe-page/recipe-page-content/recipe-page-content.css +++ b/src/app/pages/recipe-page/recipe-page-content/recipe-page-content.css @@ -16,7 +16,7 @@ article { align-items: center; } -#star { +#star-label { display: flex; align-items: center; column-gap: 3px; diff --git a/src/app/pages/recipe-page/recipe-page-content/recipe-page-content.html b/src/app/pages/recipe-page/recipe-page-content/recipe-page-content.html index e7a092c..339a0f9 100644 --- a/src/app/pages/recipe-page/recipe-page-content/recipe-page-content.html +++ b/src/app/pages/recipe-page/recipe-page-content/recipe-page-content.html @@ -4,10 +4,12 @@

{{ recipe.title }}

@if (isLoggedIn()) { - } @else {
diff --git a/src/app/pages/recipe-page/recipe-page-content/recipe-page-content.ts b/src/app/pages/recipe-page/recipe-page-content/recipe-page-content.ts index dce9479..9687070 100644 --- a/src/app/pages/recipe-page/recipe-page-content/recipe-page-content.ts +++ b/src/app/pages/recipe-page/recipe-page-content/recipe-page-content.ts @@ -7,10 +7,11 @@ import { FaIconComponent } from '@fortawesome/angular-fontawesome'; import { RecipeService } from '../../../shared/services/RecipeService'; import { AuthService } from '../../../shared/services/AuthService'; import { RecipeCommentsList } from '../../../shared/components/recipe-comments-list/recipe-comments-list'; +import { MatButton } from '@angular/material/button'; @Component({ selector: 'app-recipe-page-content', - imports: [FaIconComponent, RecipeCommentsList], + imports: [FaIconComponent, RecipeCommentsList, MatButton], templateUrl: './recipe-page-content.html', styleUrl: './recipe-page-content.css', }) diff --git a/src/app/pages/recipe-upload-page/recipe-upload-page.css b/src/app/pages/recipe-upload-page/recipe-upload-page.css index 98fb55b..0a3e589 100644 --- a/src/app/pages/recipe-upload-page/recipe-upload-page.css +++ b/src/app/pages/recipe-upload-page/recipe-upload-page.css @@ -21,15 +21,15 @@ form { column-gap: 5px; } -button { - width: 100%; -} +/*button {*/ +/* width: 100%;*/ +/*}*/ -input[type='text'], -textarea { - padding: 10px; - font-size: 16px; -} +/*input[type="text"],*/ +/*textarea {*/ +/* padding: 10px;*/ +/* font-size: 16px;*/ +/*}*/ textarea { box-sizing: border-box; diff --git a/src/app/pages/recipe-upload-page/recipe-upload-page.html b/src/app/pages/recipe-upload-page/recipe-upload-page.html index a05862e..63a1fc6 100644 --- a/src/app/pages/recipe-upload-page/recipe-upload-page.html +++ b/src/app/pages/recipe-upload-page/recipe-upload-page.html @@ -3,14 +3,12 @@

Auto-Complete Recipe (Optional)

-

- Choose a photo of a recipe from your files, and AI will fill out the form below for you. -

+

Choose a photo of a recipe from your files, and AI will fill out the form below for you.

- - + @@ -22,21 +20,20 @@

Recipe Form

- - - + + Recipe Title + + + + Recipe Text + + +
@if (sourceRecipeImage()) { diff --git a/src/app/pages/recipe-upload-page/recipe-upload-page.ts b/src/app/pages/recipe-upload-page/recipe-upload-page.ts index 188d0fd..481c605 100644 --- a/src/app/pages/recipe-upload-page/recipe-upload-page.ts +++ b/src/app/pages/recipe-upload-page/recipe-upload-page.ts @@ -1,17 +1,13 @@ import { Component, inject, signal } from '@angular/core'; -import { - FormBuilder, - FormControl, - FormGroup, - ReactiveFormsModule, - Validators, -} from '@angular/forms'; +import { FormBuilder, FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; import { SseClient } from 'ngx-sse-client'; import { Spinner } from '../../shared/components/spinner/spinner'; +import { MatButton } from '@angular/material/button'; +import { MatFormField, MatInput, MatLabel } from '@angular/material/input'; @Component({ selector: 'app-recipe-upload-page', - imports: [ReactiveFormsModule, Spinner], + imports: [ReactiveFormsModule, Spinner, MatButton, MatFormField, MatInput, MatLabel], templateUrl: './recipe-upload-page.html', styleUrl: './recipe-upload-page.css', }) @@ -83,9 +79,7 @@ export class RecipeUploadPage { }); // must do this so we auto-resize the textarea - document - .getElementById('recipe-text') - ?.dispatchEvent(new Event('input', { bubbles: true })); + document.getElementById('recipe-text')?.dispatchEvent(new Event('input', { bubbles: true })); } }, complete: () => { diff --git a/src/app/pages/recipes-search-page/recipes-search-page.css b/src/app/pages/recipes-search-page/recipes-search-page.css index e69de29..9057000 100644 --- a/src/app/pages/recipes-search-page/recipes-search-page.css +++ b/src/app/pages/recipes-search-page/recipes-search-page.css @@ -0,0 +1,5 @@ +#search-recipes-form { + display: flex; + flex-direction: column; + width: 66%; +} diff --git a/src/app/pages/recipes-search-page/recipes-search-page.html b/src/app/pages/recipes-search-page/recipes-search-page.html index 9ecb80a..e443239 100644 --- a/src/app/pages/recipes-search-page/recipes-search-page.html +++ b/src/app/pages/recipes-search-page/recipes-search-page.html @@ -1,8 +1,10 @@

Search Recipes

- - - + + Search Prompt + + +
@if (givenPrompt() !== null) { @if (resultsQuery.isLoading()) { diff --git a/src/app/pages/recipes-search-page/recipes-search-page.ts b/src/app/pages/recipes-search-page/recipes-search-page.ts index 26b0477..672e65d 100644 --- a/src/app/pages/recipes-search-page/recipes-search-page.ts +++ b/src/app/pages/recipes-search-page/recipes-search-page.ts @@ -4,10 +4,12 @@ import { injectQuery } from '@tanstack/angular-query-experimental'; import { RecipeService } from '../../shared/services/RecipeService'; import { RecipeCardGrid } from '../../shared/components/recipe-card-grid/recipe-card-grid'; import { ActivatedRoute, Router } from '@angular/router'; +import { MatButton } from '@angular/material/button'; +import { MatFormField, MatInput, MatLabel } from '@angular/material/input'; @Component({ selector: 'app-recipes-search-page', - imports: [ReactiveFormsModule, RecipeCardGrid], + imports: [ReactiveFormsModule, RecipeCardGrid, MatButton, MatFormField, MatInput, MatLabel], templateUrl: './recipes-search-page.html', styleUrl: './recipes-search-page.css', }) diff --git a/src/app/shared/components/header/header.html b/src/app/shared/components/header/header.html index 3edc6e2..25e0265 100644 --- a/src/app/shared/components/header/header.html +++ b/src/app/shared/components/header/header.html @@ -3,11 +3,11 @@ @if (isLoggedIn()) {

Welcome {{ username() }}!

- +
} @else {
- +
} diff --git a/src/app/shared/components/header/header.ts b/src/app/shared/components/header/header.ts index 9913af9..a724ba6 100644 --- a/src/app/shared/components/header/header.ts +++ b/src/app/shared/components/header/header.ts @@ -1,9 +1,10 @@ import { Component, computed, inject } from '@angular/core'; import { AuthService } from '../../services/AuthService'; +import { MatButton } from '@angular/material/button'; @Component({ selector: 'app-header', - imports: [], + imports: [MatButton], templateUrl: './header.html', styleUrl: './header.css', }) diff --git a/src/app/shared/components/recipe-comments-list/recipe-comments-list.html b/src/app/shared/components/recipe-comments-list/recipe-comments-list.html index 7d59a85..7b79ca5 100644 --- a/src/app/shared/components/recipe-comments-list/recipe-comments-list.html +++ b/src/app/shared/components/recipe-comments-list/recipe-comments-list.html @@ -31,9 +31,7 @@
  • {{ recipeComment.owner.username }} - {{ - recipeComment.created | dateTimeFormat - }} + {{ recipeComment.created | dateTimeFormat }}
  • diff --git a/src/app/shared/interceptors/auth.interceptor.ts b/src/app/shared/interceptors/auth.interceptor.ts index 0e63e91..0870c34 100644 --- a/src/app/shared/interceptors/auth.interceptor.ts +++ b/src/app/shared/interceptors/auth.interceptor.ts @@ -36,9 +36,7 @@ export const authInterceptor: HttpInterceptorFn = (req, next) => { } else if (error.status === 401 && error.url?.endsWith('auth/refresh')) { // our refresh token is expired // redirect to login page - return from(router.navigate(['/'])).pipe( - switchMap(() => throwError(() => error)), - ); + return from(router.navigate(['/'])).pipe(switchMap(() => throwError(() => error))); } else { return throwError(() => error); } diff --git a/src/app/shared/services/EndpointService.ts b/src/app/shared/services/EndpointService.ts index f2ebc7c..3ac271a 100644 --- a/src/app/shared/services/EndpointService.ts +++ b/src/app/shared/services/EndpointService.ts @@ -6,11 +6,7 @@ import { QueryParams } from '../models/Query.model'; providedIn: 'root', }) export class EndpointService { - public getUrl( - endpoint: keyof typeof Endpoints, - pathParams?: string[], - queryParams?: QueryParams, - ): string { + public getUrl(endpoint: keyof typeof Endpoints, pathParams?: string[], queryParams?: QueryParams): string { const urlSearchParams = new URLSearchParams(); if (queryParams?.page !== undefined) { urlSearchParams.set('page', queryParams.page.toString()); diff --git a/src/app/shared/services/RecipeService.ts b/src/app/shared/services/RecipeService.ts index 9d0c20c..9068953 100644 --- a/src/app/shared/services/RecipeService.ts +++ b/src/app/shared/services/RecipeService.ts @@ -19,16 +19,12 @@ export class RecipeService { public getRecipes(): Promise { return firstValueFrom( - this.http - .get('http://localhost:8080/recipes') - .pipe(map((res) => res.content)), + this.http.get('http://localhost:8080/recipes').pipe(map((res) => res.content)), ); } public getRecipeView(username: string, slug: string): Promise { - return firstValueFrom( - this.http.get(`http://localhost:8080/recipes/${username}/${slug}`), - ); + return firstValueFrom(this.http.get(`http://localhost:8080/recipes/${username}/${slug}`)); } private getRecipeUrl(recipeView: RecipeView): string { @@ -50,11 +46,7 @@ export class RecipeService { } } - public getComments( - username: string, - slug: string, - queryParams?: QueryParams, - ): Promise { + public getComments(username: string, slug: string, queryParams?: QueryParams): Promise { return firstValueFrom( this.http.get( this.endpointService.getUrl('recipes', [username, slug, 'comments'], queryParams), @@ -62,18 +54,11 @@ export class RecipeService { ); } - public async addComment( - username: string, - slug: string, - commentText: string, - ): Promise { + public async addComment(username: string, slug: string, commentText: string): Promise { const comment = await firstValueFrom( - this.http.post( - `http://localhost:8080/recipes/${username}/${slug}/comments`, - { - text: commentText, - }, - ), + this.http.post(`http://localhost:8080/recipes/${username}/${slug}/comments`, { + text: commentText, + }), ); await this.queryClient.invalidateQueries({ queryKey: ['recipeComments', username, slug], diff --git a/src/index.html b/src/index.html index 78734f4..7b66269 100644 --- a/src/index.html +++ b/src/index.html @@ -14,6 +14,7 @@ href="https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap" rel="stylesheet" /> + diff --git a/src/material-theme.scss b/src/material-theme.scss new file mode 100644 index 0000000..0b32586 --- /dev/null +++ b/src/material-theme.scss @@ -0,0 +1,43 @@ +// Include theming for Angular Material with `mat.theme()`. +// This Sass mixin will define CSS variables that are used for styling Angular Material +// components according to the Material 3 design spec. +// Learn more about theming and how to use it for your application's +// custom components at https://material.angular.dev/guide/theming +@use "@angular/material" as mat; +@use "theme-colors" as themeColors; + +html { + height: 100%; + @include mat.theme( + ( + color: ( + primary: themeColors.$primary-palette, + tertiary: themeColors.$tertiary-palette, + ), + typography: Inter, + density: 0, + ) + ); +} + +@include mat.form-field-overrides(( + filled-container-color: var(--mat-sys-surface-variant), +)); + +body { + // Default the application to a light color theme. This can be changed to + // `dark` to enable the dark color theme, or to `light dark` to defer to the + // user's system settings. + color-scheme: light; + + // Set a default background, font and text colors for the application using + // Angular Material's system-level CSS variables. Learn more about these + // variables at https://material.angular.dev/guide/system-variables + background-color: var(--mat-sys-surface); + color: var(--mat-sys-on-surface); + font: var(--mat-sys-body-medium); + + // Reset the user agent margin. + margin: 0; + height: 100%; +} diff --git a/src/styles.css b/src/styles.css index 07d4a61..3c2722c 100644 --- a/src/styles.css +++ b/src/styles.css @@ -1,7 +1,7 @@ -@import url('https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap'); +@import url("https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap"); :root { - --primary-font-family: 'Inter'; + --primary-font-family: "Inter"; --primary-white: #ffffff; --primary-red: #91351d; --primary-yellow: #ffb61d; @@ -34,17 +34,17 @@ a:hover { color: hsl(from var(--primary-red) h s l / 0.9); } -button { - background-color: var(--off-white-2); - border: none; - border-radius: 5px; - padding: 10px; -} +/*button {*/ +/* background-color: var(--off-white-2);*/ +/* border: none;*/ +/* border-radius: 5px;*/ +/* padding: 10px;*/ +/*}*/ -button:hover:not(:disabled) { - background-color: var(--off-white-1); - cursor: pointer; -} +/*button:hover:not(:disabled) {*/ +/* background-color: var(--off-white-1);*/ +/* cursor: pointer;*/ +/*}*/ fa-icon { color: var(--primary-red); diff --git a/src/theme-colors.scss b/src/theme-colors.scss new file mode 100644 index 0000000..b24f33b --- /dev/null +++ b/src/theme-colors.scss @@ -0,0 +1,137 @@ +// This file was generated by running 'ng generate @angular/material:theme-color'. +// Proceed with caution if making changes to this file. + +@use 'sass:map'; +@use '@angular/material' as mat; + +// Note: Color palettes are generated from primary: #91351d, secondary: #ffb61d, neutral: #aaa, neutral variant: #252525 +$_palettes: ( + primary: ( + 0: #000000, + 10: #3c0800, + 20: #611200, + 25: #711e07, + 30: #802912, + 35: #90341c, + 40: #a04027, + 50: #c0573c, + 60: #e07053, + 70: #ff8b6d, + 80: #ffb4a2, + 90: #ffdbd2, + 95: #ffede9, + 98: #fff8f6, + 99: #fffbff, + 100: #ffffff, + ), + secondary: ( + 0: #000000, + 10: #281900, + 20: #432c00, + 25: #513600, + 30: #5f4100, + 35: #6f4c00, + 40: #7e5700, + 50: #9e6e00, + 60: #bf8600, + 70: #e29f00, + 80: #ffba36, + 90: #ffdeac, + 95: #ffeed9, + 98: #fff8f3, + 99: #fffbff, + 100: #ffffff, + ), + tertiary: ( + 0: #000000, + 10: #241a00, + 20: #3d2f00, + 25: #4a3900, + 30: #584400, + 35: #665000, + 40: #735b0d, + 50: #8e7426, + 60: #aa8e3e, + 70: #c6a855, + 80: #e3c36d, + 90: #ffe08e, + 95: #ffefce, + 98: #fff8f1, + 99: #fffbff, + 100: #ffffff, + ), + neutral: ( + 0: #000000, + 10: #1a1c1c, + 20: #2f3131, + 25: #3a3c3c, + 30: #464747, + 35: #525253, + 40: #5e5e5f, + 50: #767777, + 60: #909191, + 70: #ababab, + 80: #c7c6c6, + 90: #e3e2e2, + 95: #f1f0f0, + 98: #faf9f9, + 99: #fdfcfc, + 100: #ffffff, + 4: #0d0e0f, + 6: #121414, + 12: #1e2020, + 17: #292a2a, + 22: #343535, + 24: #38393a, + 87: #dadada, + 92: #e9e8e8, + 94: #eeeeed, + 96: #f4f3f3, + ), + neutral-variant: ( + 0: #000000, + 10: #1b1c1c, + 20: #303030, + 25: #3c3b3b, + 30: #474746, + 35: #535252, + 40: #5f5e5e, + 50: #787776, + 60: #929090, + 70: #adabaa, + 80: #c8c6c5, + 90: #e4e2e1, + 95: #f3f0ef, + 98: #fcf9f8, + 99: #fffbfb, + 100: #ffffff, + ), + error: ( + 0: #000000, + 10: #410002, + 20: #690005, + 25: #7e0007, + 30: #93000a, + 35: #a80710, + 40: #ba1a1a, + 50: #de3730, + 60: #ff5449, + 70: #ff897d, + 80: #ffb4ab, + 90: #ffdad6, + 95: #ffedea, + 98: #fff8f7, + 99: #fffbff, + 100: #ffffff, + ), +); + +$_rest: ( + secondary: map.get($_palettes, secondary), + neutral: map.get($_palettes, neutral), + neutral-variant: map.get($_palettes, neutral-variant), + error: map.get($_palettes, error), +); + +$primary-palette: map.merge(map.get($_palettes, primary), $_rest); +$tertiary-palette: map.merge(map.get($_palettes, tertiary), $_rest);