feat(REQ-001): scaffold Ionic 8 + Angular 20 + Capacitor project
- Initialize Ionic tabs project with Angular 20 and Capacitor 8 - Configure 4-tab layout: Journal, Stats, Plans, Profile - Create feature modules with lazy loading (home, stats, search, profile) - Add core services stubs (meal, calorie-estimator, auth, storage) - Add modal placeholders for manual and LLM input - Apply Precision Vitality design system (colors, fonts, tonal layering) - Configure Lexend + Plus Jakarta Sans typography - Implement no-border design rule with tonal surface hierarchy - Add food dictionary for AI calorie estimation - Configure environment files with Firebase placeholders - Set Capacitor appId: run.i_a.calorietracker - Verify: ionic build and cap sync pass successfully Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
15
.browserslistrc
Normal file
15
.browserslistrc
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
|
||||||
|
# For additional information regarding the format and rule options, please see:
|
||||||
|
# https://github.com/browserslist/browserslist#queries
|
||||||
|
|
||||||
|
# For the full list of supported browsers by the Angular framework, please see:
|
||||||
|
# https://angular.dev/reference/versions#browser-support
|
||||||
|
|
||||||
|
# You can see what browsers were selected by your queries by running:
|
||||||
|
# npx browserslist
|
||||||
|
|
||||||
|
Chrome >=107
|
||||||
|
Firefox >=106
|
||||||
|
Edge >=107
|
||||||
|
Safari >=16.1
|
||||||
|
iOS >=16.1
|
||||||
16
.editorconfig
Normal file
16
.editorconfig
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# Editor configuration, see https://editorconfig.org
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
[*.ts]
|
||||||
|
quote_type = single
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
max_line_length = off
|
||||||
|
trim_trailing_whitespace = false
|
||||||
47
.eslintrc.json
Normal file
47
.eslintrc.json
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
{
|
||||||
|
"root": true,
|
||||||
|
"ignorePatterns": ["projects/**/*"],
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": ["*.ts"],
|
||||||
|
"parserOptions": {
|
||||||
|
"project": ["tsconfig.json"],
|
||||||
|
"createDefaultProgram": true
|
||||||
|
},
|
||||||
|
"extends": [
|
||||||
|
"plugin:@angular-eslint/recommended",
|
||||||
|
"plugin:@angular-eslint/template/process-inline-templates"
|
||||||
|
],
|
||||||
|
"rules": {
|
||||||
|
"@angular-eslint/prefer-standalone": "off",
|
||||||
|
"@angular-eslint/component-class-suffix": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"suffixes": ["Page", "Component"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"@angular-eslint/component-selector": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"type": "element",
|
||||||
|
"prefix": "app",
|
||||||
|
"style": "kebab-case"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"@angular-eslint/directive-selector": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"type": "attribute",
|
||||||
|
"prefix": "app",
|
||||||
|
"style": "camelCase"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"files": ["*.html"],
|
||||||
|
"extends": ["plugin:@angular-eslint/template/recommended"],
|
||||||
|
"rules": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
12
.gitignore
vendored
12
.gitignore
vendored
@@ -36,6 +36,18 @@ Thumbs.db
|
|||||||
*.keystore
|
*.keystore
|
||||||
*.jks
|
*.jks
|
||||||
|
|
||||||
|
# Ionic/Angular generated
|
||||||
|
.angular/
|
||||||
|
.sass-cache/
|
||||||
|
connect.lock
|
||||||
|
coverage/
|
||||||
|
e2e/
|
||||||
|
testem.log
|
||||||
|
typings/
|
||||||
|
|
||||||
|
# Temp scaffold
|
||||||
|
tmp-ionic-scaffold/
|
||||||
|
|
||||||
# Misc
|
# Misc
|
||||||
*.log
|
*.log
|
||||||
npm-debug.log*
|
npm-debug.log*
|
||||||
|
|||||||
149
angular.json
Normal file
149
angular.json
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
{
|
||||||
|
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||||
|
"version": 1,
|
||||||
|
"newProjectRoot": "projects",
|
||||||
|
"projects": {
|
||||||
|
"app": {
|
||||||
|
"projectType": "application",
|
||||||
|
"schematics": {},
|
||||||
|
"root": "",
|
||||||
|
"sourceRoot": "src",
|
||||||
|
"prefix": "app",
|
||||||
|
"architect": {
|
||||||
|
"build": {
|
||||||
|
"builder": "@angular-devkit/build-angular:browser",
|
||||||
|
"options": {
|
||||||
|
"outputPath": "www",
|
||||||
|
"index": "src/index.html",
|
||||||
|
"main": "src/main.ts",
|
||||||
|
"polyfills": "src/polyfills.ts",
|
||||||
|
"tsConfig": "tsconfig.app.json",
|
||||||
|
"inlineStyleLanguage": "scss",
|
||||||
|
"assets": [
|
||||||
|
{
|
||||||
|
"glob": "**/*",
|
||||||
|
"input": "src/assets",
|
||||||
|
"output": "assets"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"glob": "**/*.svg",
|
||||||
|
"input": "node_modules/ionicons/dist/ionicons/svg",
|
||||||
|
"output": "./svg"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"styles": ["src/global.scss", "src/theme/variables.scss"],
|
||||||
|
"scripts": []
|
||||||
|
},
|
||||||
|
"configurations": {
|
||||||
|
"production": {
|
||||||
|
"budgets": [
|
||||||
|
{
|
||||||
|
"type": "initial",
|
||||||
|
"maximumWarning": "2mb",
|
||||||
|
"maximumError": "5mb"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "anyComponentStyle",
|
||||||
|
"maximumWarning": "2kb",
|
||||||
|
"maximumError": "4kb"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fileReplacements": [
|
||||||
|
{
|
||||||
|
"replace": "src/environments/environment.ts",
|
||||||
|
"with": "src/environments/environment.prod.ts"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputHashing": "all"
|
||||||
|
},
|
||||||
|
"development": {
|
||||||
|
"buildOptimizer": false,
|
||||||
|
"optimization": false,
|
||||||
|
"vendorChunk": true,
|
||||||
|
"extractLicenses": false,
|
||||||
|
"sourceMap": true,
|
||||||
|
"namedChunks": true
|
||||||
|
},
|
||||||
|
"ci": {
|
||||||
|
"progress": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"defaultConfiguration": "production"
|
||||||
|
},
|
||||||
|
"serve": {
|
||||||
|
"builder": "@angular-devkit/build-angular:dev-server",
|
||||||
|
"configurations": {
|
||||||
|
"production": {
|
||||||
|
"buildTarget": "app:build:production"
|
||||||
|
},
|
||||||
|
"development": {
|
||||||
|
"buildTarget": "app:build:development"
|
||||||
|
},
|
||||||
|
"ci": {
|
||||||
|
"progress": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"defaultConfiguration": "development"
|
||||||
|
},
|
||||||
|
"extract-i18n": {
|
||||||
|
"builder": "@angular-devkit/build-angular:extract-i18n",
|
||||||
|
"options": {
|
||||||
|
"buildTarget": "app:build"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"test": {
|
||||||
|
"builder": "@angular-devkit/build-angular:karma",
|
||||||
|
"options": {
|
||||||
|
"main": "src/test.ts",
|
||||||
|
"polyfills": "src/polyfills.ts",
|
||||||
|
"tsConfig": "tsconfig.spec.json",
|
||||||
|
"karmaConfig": "karma.conf.js",
|
||||||
|
"inlineStyleLanguage": "scss",
|
||||||
|
"assets": [
|
||||||
|
{
|
||||||
|
"glob": "**/*",
|
||||||
|
"input": "src/assets",
|
||||||
|
"output": "assets"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"glob": "**/*.svg",
|
||||||
|
"input": "node_modules/ionicons/dist/ionicons/svg",
|
||||||
|
"output": "./svg"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"styles": ["src/global.scss", "src/theme/variables.scss"],
|
||||||
|
"scripts": []
|
||||||
|
},
|
||||||
|
"configurations": {
|
||||||
|
"ci": {
|
||||||
|
"progress": false,
|
||||||
|
"watch": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lint": {
|
||||||
|
"builder": "@angular-eslint/builder:lint",
|
||||||
|
"options": {
|
||||||
|
"lintFilePatterns": [
|
||||||
|
"src/**/*.ts",
|
||||||
|
"src/**/*.html"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"cli": {
|
||||||
|
"schematicCollections": [
|
||||||
|
"@ionic/angular-toolkit"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"schematics": {
|
||||||
|
"@ionic/angular-toolkit:component": {
|
||||||
|
"styleext": "scss"
|
||||||
|
},
|
||||||
|
"@ionic/angular-toolkit:page": {
|
||||||
|
"styleext": "scss"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
12
capacitor.config.ts
Normal file
12
capacitor.config.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import type { CapacitorConfig } from '@capacitor/cli';
|
||||||
|
|
||||||
|
const config: CapacitorConfig = {
|
||||||
|
appId: 'run.i_a.calorietracker',
|
||||||
|
appName: 'Precision Vitality',
|
||||||
|
webDir: 'www',
|
||||||
|
server: {
|
||||||
|
androidScheme: 'https'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
7
ionic.config.json
Normal file
7
ionic.config.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"name": "precision-vitality",
|
||||||
|
"integrations": {
|
||||||
|
"capacitor": {}
|
||||||
|
},
|
||||||
|
"type": "angular"
|
||||||
|
}
|
||||||
12
ionic.starter.json
Normal file
12
ionic.starter.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"name": "Tabs Starter",
|
||||||
|
"baseref": "main",
|
||||||
|
"tarignore": [
|
||||||
|
"node_modules",
|
||||||
|
"package-lock.json",
|
||||||
|
"www"
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"test": "npm run lint && npm run build && npm run test -- --configuration=ci --browsers=ChromeHeadless"
|
||||||
|
}
|
||||||
|
}
|
||||||
44
karma.conf.js
Normal file
44
karma.conf.js
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
// Karma configuration file, see link for more information
|
||||||
|
// https://karma-runner.github.io/1.0/config/configuration-file.html
|
||||||
|
|
||||||
|
module.exports = function (config) {
|
||||||
|
config.set({
|
||||||
|
basePath: '',
|
||||||
|
frameworks: ['jasmine', '@angular-devkit/build-angular'],
|
||||||
|
plugins: [
|
||||||
|
require('karma-jasmine'),
|
||||||
|
require('karma-chrome-launcher'),
|
||||||
|
require('karma-jasmine-html-reporter'),
|
||||||
|
require('karma-coverage'),
|
||||||
|
require('@angular-devkit/build-angular/plugins/karma')
|
||||||
|
],
|
||||||
|
client: {
|
||||||
|
jasmine: {
|
||||||
|
// you can add configuration options for Jasmine here
|
||||||
|
// the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html
|
||||||
|
// for example, you can disable the random execution with `random: false`
|
||||||
|
// or set a specific seed with `seed: 4321`
|
||||||
|
},
|
||||||
|
clearContext: false // leave Jasmine Spec Runner output visible in browser
|
||||||
|
},
|
||||||
|
jasmineHtmlReporter: {
|
||||||
|
suppressAll: true // removes the duplicated traces
|
||||||
|
},
|
||||||
|
coverageReporter: {
|
||||||
|
dir: require('path').join(__dirname, './coverage/app'),
|
||||||
|
subdir: '.',
|
||||||
|
reporters: [
|
||||||
|
{ type: 'html' },
|
||||||
|
{ type: 'text-summary' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
reporters: ['progress', 'kjhtml'],
|
||||||
|
port: 9876,
|
||||||
|
colors: true,
|
||||||
|
logLevel: config.LOG_INFO,
|
||||||
|
autoWatch: true,
|
||||||
|
browsers: ['Chrome'],
|
||||||
|
singleRun: false,
|
||||||
|
restartOnFileChange: true
|
||||||
|
});
|
||||||
|
};
|
||||||
19368
package-lock.json
generated
Normal file
19368
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
65
package.json
Normal file
65
package.json
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
{
|
||||||
|
"name": "precision-vitality",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"author": "Emanuele",
|
||||||
|
"homepage": "https://git.i-a.run/emanuele/traccia-calorica",
|
||||||
|
"scripts": {
|
||||||
|
"ng": "ng",
|
||||||
|
"start": "ng serve",
|
||||||
|
"build": "ng build",
|
||||||
|
"watch": "ng build --watch --configuration development",
|
||||||
|
"test": "ng test",
|
||||||
|
"lint": "ng lint"
|
||||||
|
},
|
||||||
|
"private": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@angular/animations": "^20.0.0",
|
||||||
|
"@angular/common": "^20.0.0",
|
||||||
|
"@angular/compiler": "^20.0.0",
|
||||||
|
"@angular/core": "^20.0.0",
|
||||||
|
"@angular/forms": "^20.0.0",
|
||||||
|
"@angular/platform-browser": "^20.0.0",
|
||||||
|
"@angular/platform-browser-dynamic": "^20.0.0",
|
||||||
|
"@angular/router": "^20.0.0",
|
||||||
|
"@capacitor/app": "8.1.0",
|
||||||
|
"@capacitor/core": "8.3.0",
|
||||||
|
"@capacitor/haptics": "8.0.2",
|
||||||
|
"@capacitor/keyboard": "8.0.3",
|
||||||
|
"@capacitor/status-bar": "8.0.2",
|
||||||
|
"@ionic/angular": "^8.0.0",
|
||||||
|
"@ionic/storage-angular": "^4.0.0",
|
||||||
|
"ionicons": "^7.0.0",
|
||||||
|
"rxjs": "~7.8.0",
|
||||||
|
"tslib": "^2.3.0",
|
||||||
|
"zone.js": "~0.15.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@angular-devkit/build-angular": "^20.0.0",
|
||||||
|
"@angular-eslint/builder": "^20.0.0",
|
||||||
|
"@angular-eslint/eslint-plugin": "^20.0.0",
|
||||||
|
"@angular-eslint/eslint-plugin-template": "^20.0.0",
|
||||||
|
"@angular-eslint/schematics": "^20.0.0",
|
||||||
|
"@angular-eslint/template-parser": "^20.0.0",
|
||||||
|
"@angular/cli": "^20.0.0",
|
||||||
|
"@angular/compiler-cli": "^20.0.0",
|
||||||
|
"@angular/language-service": "^20.0.0",
|
||||||
|
"@capacitor/cli": "8.3.0",
|
||||||
|
"@ionic/angular-toolkit": "^12.0.0",
|
||||||
|
"@types/jasmine": "~5.1.0",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^8.18.0",
|
||||||
|
"@typescript-eslint/parser": "^8.18.0",
|
||||||
|
"eslint": "^9.16.0",
|
||||||
|
"eslint-plugin-import": "^2.29.1",
|
||||||
|
"eslint-plugin-jsdoc": "^48.2.1",
|
||||||
|
"eslint-plugin-prefer-arrow": "1.2.2",
|
||||||
|
"jasmine-core": "~5.1.0",
|
||||||
|
"jasmine-spec-reporter": "~5.0.0",
|
||||||
|
"karma": "~6.4.0",
|
||||||
|
"karma-chrome-launcher": "~3.2.0",
|
||||||
|
"karma-coverage": "~2.2.0",
|
||||||
|
"karma-jasmine": "~5.1.0",
|
||||||
|
"karma-jasmine-html-reporter": "~2.1.0",
|
||||||
|
"typescript": "~5.9.0"
|
||||||
|
},
|
||||||
|
"description": "Precision Vitality — Calorie Tracker with AI estimation"
|
||||||
|
}
|
||||||
16
src/app/app-routing.module.ts
Normal file
16
src/app/app-routing.module.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { PreloadAllModules, RouterModule, Routes } from '@angular/router';
|
||||||
|
|
||||||
|
const routes: Routes = [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
loadChildren: () => import('./tabs/tabs.module').then(m => m.TabsPageModule)
|
||||||
|
}
|
||||||
|
];
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
RouterModule.forRoot(routes, { preloadingStrategy: PreloadAllModules })
|
||||||
|
],
|
||||||
|
exports: [RouterModule]
|
||||||
|
})
|
||||||
|
export class AppRoutingModule {}
|
||||||
3
src/app/app.component.html
Normal file
3
src/app/app.component.html
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<ion-app>
|
||||||
|
<ion-router-outlet></ion-router-outlet>
|
||||||
|
</ion-app>
|
||||||
0
src/app/app.component.scss
Normal file
0
src/app/app.component.scss
Normal file
21
src/app/app.component.spec.ts
Normal file
21
src/app/app.component.spec.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||||
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { AppComponent } from './app.component';
|
||||||
|
|
||||||
|
describe('AppComponent', () => {
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [AppComponent],
|
||||||
|
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||||
|
}).compileComponents();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create the app', () => {
|
||||||
|
const fixture = TestBed.createComponent(AppComponent);
|
||||||
|
const app = fixture.componentInstance;
|
||||||
|
expect(app).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
11
src/app/app.component.ts
Normal file
11
src/app/app.component.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-root',
|
||||||
|
templateUrl: 'app.component.html',
|
||||||
|
styleUrls: ['app.component.scss'],
|
||||||
|
standalone: false,
|
||||||
|
})
|
||||||
|
export class AppComponent {
|
||||||
|
constructor() {}
|
||||||
|
}
|
||||||
24
src/app/app.module.ts
Normal file
24
src/app/app.module.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { BrowserModule } from '@angular/platform-browser';
|
||||||
|
import { RouteReuseStrategy } from '@angular/router';
|
||||||
|
import { HttpClientModule } from '@angular/common/http';
|
||||||
|
|
||||||
|
import { IonicModule, IonicRouteStrategy } from '@ionic/angular';
|
||||||
|
import { IonicStorageModule } from '@ionic/storage-angular';
|
||||||
|
|
||||||
|
import { AppRoutingModule } from './app-routing.module';
|
||||||
|
import { AppComponent } from './app.component';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [AppComponent],
|
||||||
|
imports: [
|
||||||
|
BrowserModule,
|
||||||
|
HttpClientModule,
|
||||||
|
IonicModule.forRoot(),
|
||||||
|
IonicStorageModule.forRoot(),
|
||||||
|
AppRoutingModule
|
||||||
|
],
|
||||||
|
providers: [{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy }],
|
||||||
|
bootstrap: [AppComponent],
|
||||||
|
})
|
||||||
|
export class AppModule {}
|
||||||
24
src/app/core/models/meal.model.ts
Normal file
24
src/app/core/models/meal.model.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
export interface Meal {
|
||||||
|
id: string;
|
||||||
|
timestamp: Date;
|
||||||
|
calories: number;
|
||||||
|
inputType: 'manual' | 'llm';
|
||||||
|
description?: string;
|
||||||
|
llmConfidence?: 'high' | 'medium' | 'low';
|
||||||
|
llmExplanation?: string;
|
||||||
|
createdAt: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MealInput {
|
||||||
|
calories: number;
|
||||||
|
inputType: 'manual' | 'llm';
|
||||||
|
description?: string;
|
||||||
|
llmConfidence?: 'high' | 'medium' | 'low';
|
||||||
|
llmExplanation?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CalorieEstimate {
|
||||||
|
calories: number;
|
||||||
|
confidence: 'high' | 'medium' | 'low';
|
||||||
|
explanation: string;
|
||||||
|
}
|
||||||
7
src/app/core/services/auth.service.ts
Normal file
7
src/app/core/services/auth.service.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
|
||||||
|
@Injectable({ providedIn: 'root' })
|
||||||
|
export class AuthService {
|
||||||
|
// TODO: Implement in REQ-002 with Firebase Auth
|
||||||
|
constructor() {}
|
||||||
|
}
|
||||||
7
src/app/core/services/calorie-estimator.service.ts
Normal file
7
src/app/core/services/calorie-estimator.service.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
|
||||||
|
@Injectable({ providedIn: 'root' })
|
||||||
|
export class CalorieEstimatorService {
|
||||||
|
// TODO: Implement in REQ-006 with Claude API proxy
|
||||||
|
constructor() {}
|
||||||
|
}
|
||||||
7
src/app/core/services/meal.service.ts
Normal file
7
src/app/core/services/meal.service.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
|
||||||
|
@Injectable({ providedIn: 'root' })
|
||||||
|
export class MealService {
|
||||||
|
// TODO: Implement in REQ-007 with Firestore
|
||||||
|
constructor() {}
|
||||||
|
}
|
||||||
27
src/app/core/services/storage.service.ts
Normal file
27
src/app/core/services/storage.service.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { Storage } from '@ionic/storage-angular';
|
||||||
|
|
||||||
|
const CALORIE_TARGET_KEY = 'calorieTarget';
|
||||||
|
const DEFAULT_TARGET = 2000;
|
||||||
|
|
||||||
|
@Injectable({ providedIn: 'root' })
|
||||||
|
export class StorageService {
|
||||||
|
private _storage: Storage | null = null;
|
||||||
|
|
||||||
|
constructor(private storage: Storage) {
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
async init() {
|
||||||
|
this._storage = await this.storage.create();
|
||||||
|
}
|
||||||
|
|
||||||
|
async getCalorieTarget(): Promise<number> {
|
||||||
|
const val = await this._storage?.get(CALORIE_TARGET_KEY);
|
||||||
|
return val ?? DEFAULT_TARGET;
|
||||||
|
}
|
||||||
|
|
||||||
|
async setCalorieTarget(target: number): Promise<void> {
|
||||||
|
await this._storage?.set(CALORIE_TARGET_KEY, target);
|
||||||
|
}
|
||||||
|
}
|
||||||
13
src/app/features/home/home-routing.module.ts
Normal file
13
src/app/features/home/home-routing.module.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { RouterModule, Routes } from '@angular/router';
|
||||||
|
import { HomePage } from './home.page';
|
||||||
|
|
||||||
|
const routes: Routes = [
|
||||||
|
{ path: '', component: HomePage }
|
||||||
|
];
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [RouterModule.forChild(routes)],
|
||||||
|
exports: [RouterModule]
|
||||||
|
})
|
||||||
|
export class HomePageRoutingModule {}
|
||||||
23
src/app/features/home/home.module.ts
Normal file
23
src/app/features/home/home.module.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { IonicModule } from '@ionic/angular';
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
|
import { HomePageRoutingModule } from './home-routing.module';
|
||||||
|
import { HomePage } from './home.page';
|
||||||
|
import { ManualInputModalComponent } from './modals/manual-input-modal.component';
|
||||||
|
import { LlmInputModalComponent } from './modals/llm-input-modal.component';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
IonicModule,
|
||||||
|
CommonModule,
|
||||||
|
FormsModule,
|
||||||
|
HomePageRoutingModule
|
||||||
|
],
|
||||||
|
declarations: [
|
||||||
|
HomePage,
|
||||||
|
ManualInputModalComponent,
|
||||||
|
LlmInputModalComponent
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class HomePageModule {}
|
||||||
44
src/app/features/home/home.page.html
Normal file
44
src/app/features/home/home.page.html
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
<ion-content [fullscreen]="true" class="pv-surface">
|
||||||
|
<div class="home-container">
|
||||||
|
<!-- Header -->
|
||||||
|
<div class="home-header">
|
||||||
|
<p class="pv-label-md date-label">{{ today }}</p>
|
||||||
|
<h2 class="pv-headline-sm">Il tuo progresso giornaliero</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Calorie Display -->
|
||||||
|
<div class="calorie-display">
|
||||||
|
<span class="pv-display-lg">0</span>
|
||||||
|
<p class="pv-label-md">calorie oggi</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Progress Bar -->
|
||||||
|
<div class="progress-section">
|
||||||
|
<div class="progress-labels">
|
||||||
|
<span class="pv-label-md">ATTUALE: 0</span>
|
||||||
|
<span class="pv-label-md">OBIETTIVO: 2000</span>
|
||||||
|
</div>
|
||||||
|
<div class="pv-progress-bar">
|
||||||
|
<div class="pv-progress-bar-fill" style="width: 0%"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Meal List -->
|
||||||
|
<div class="meal-section">
|
||||||
|
<h3 class="pv-headline-sm">Diario Alimentare</h3>
|
||||||
|
|
||||||
|
<!-- Empty State -->
|
||||||
|
<div class="empty-state">
|
||||||
|
<ion-icon name="restaurant-outline" class="empty-icon"></ion-icon>
|
||||||
|
<p>Nessun pasto registrato oggi. Inizia!</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- FAB -->
|
||||||
|
<ion-fab vertical="bottom" horizontal="end" slot="fixed">
|
||||||
|
<ion-fab-button class="pv-fab">
|
||||||
|
<ion-icon name="add"></ion-icon>
|
||||||
|
</ion-fab-button>
|
||||||
|
</ion-fab>
|
||||||
|
</ion-content>
|
||||||
68
src/app/features/home/home.page.scss
Normal file
68
src/app/features/home/home.page.scss
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
.home-container {
|
||||||
|
padding: 16px 20px 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-header {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
|
||||||
|
.date-label {
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.calorie-display {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
|
||||||
|
.pv-display-lg {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-section {
|
||||||
|
margin-bottom: 32px;
|
||||||
|
|
||||||
|
.progress-labels {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.meal-section {
|
||||||
|
.pv-headline-sm {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-state {
|
||||||
|
text-align: center;
|
||||||
|
padding: 48px 24px;
|
||||||
|
color: var(--pv-on-surface-variant);
|
||||||
|
|
||||||
|
.empty-icon {
|
||||||
|
font-size: 48px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-family: var(--pv-font-ui);
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.pv-fab {
|
||||||
|
--background: var(--pv-accent);
|
||||||
|
--color: white;
|
||||||
|
--box-shadow: 0px 12px 32px rgba(0, 107, 27, 0.15);
|
||||||
|
|
||||||
|
width: 64px;
|
||||||
|
height: 64px;
|
||||||
|
|
||||||
|
ion-icon {
|
||||||
|
font-size: 28px;
|
||||||
|
}
|
||||||
|
}
|
||||||
20
src/app/features/home/home.page.ts
Normal file
20
src/app/features/home/home.page.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-home',
|
||||||
|
templateUrl: 'home.page.html',
|
||||||
|
styleUrls: ['home.page.scss'],
|
||||||
|
standalone: false,
|
||||||
|
})
|
||||||
|
export class HomePage implements OnInit {
|
||||||
|
today = '';
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
const now = new Date();
|
||||||
|
this.today = now.toLocaleDateString('it-IT', {
|
||||||
|
weekday: 'long',
|
||||||
|
day: 'numeric',
|
||||||
|
month: 'long'
|
||||||
|
}).toUpperCase();
|
||||||
|
}
|
||||||
|
}
|
||||||
29
src/app/features/home/modals/llm-input-modal.component.ts
Normal file
29
src/app/features/home/modals/llm-input-modal.component.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { ModalController } from '@ionic/angular';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-llm-input-modal',
|
||||||
|
template: `
|
||||||
|
<ion-header>
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-title>Descrivi il pasto</ion-title>
|
||||||
|
<ion-buttons slot="end">
|
||||||
|
<ion-button (click)="dismiss()">
|
||||||
|
<ion-icon name="close" slot="icon-only"></ion-icon>
|
||||||
|
</ion-button>
|
||||||
|
</ion-buttons>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
<ion-content>
|
||||||
|
<p style="padding: 16px;">LLM input modal - placeholder (REQ-005)</p>
|
||||||
|
</ion-content>
|
||||||
|
`,
|
||||||
|
standalone: false,
|
||||||
|
})
|
||||||
|
export class LlmInputModalComponent {
|
||||||
|
constructor(private modalCtrl: ModalController) {}
|
||||||
|
|
||||||
|
dismiss() {
|
||||||
|
this.modalCtrl.dismiss();
|
||||||
|
}
|
||||||
|
}
|
||||||
29
src/app/features/home/modals/manual-input-modal.component.ts
Normal file
29
src/app/features/home/modals/manual-input-modal.component.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { ModalController } from '@ionic/angular';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-manual-input-modal',
|
||||||
|
template: `
|
||||||
|
<ion-header>
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-title>Inserisci calorie</ion-title>
|
||||||
|
<ion-buttons slot="end">
|
||||||
|
<ion-button (click)="dismiss()">
|
||||||
|
<ion-icon name="close" slot="icon-only"></ion-icon>
|
||||||
|
</ion-button>
|
||||||
|
</ion-buttons>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
<ion-content>
|
||||||
|
<p style="padding: 16px;">Manual input modal - placeholder (REQ-004)</p>
|
||||||
|
</ion-content>
|
||||||
|
`,
|
||||||
|
standalone: false,
|
||||||
|
})
|
||||||
|
export class ManualInputModalComponent {
|
||||||
|
constructor(private modalCtrl: ModalController) {}
|
||||||
|
|
||||||
|
dismiss() {
|
||||||
|
this.modalCtrl.dismiss();
|
||||||
|
}
|
||||||
|
}
|
||||||
13
src/app/features/profile/profile-routing.module.ts
Normal file
13
src/app/features/profile/profile-routing.module.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { RouterModule, Routes } from '@angular/router';
|
||||||
|
import { ProfilePage } from './profile.page';
|
||||||
|
|
||||||
|
const routes: Routes = [
|
||||||
|
{ path: '', component: ProfilePage }
|
||||||
|
];
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [RouterModule.forChild(routes)],
|
||||||
|
exports: [RouterModule]
|
||||||
|
})
|
||||||
|
export class ProfilePageRoutingModule {}
|
||||||
17
src/app/features/profile/profile.module.ts
Normal file
17
src/app/features/profile/profile.module.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { IonicModule } from '@ionic/angular';
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
|
import { ProfilePageRoutingModule } from './profile-routing.module';
|
||||||
|
import { ProfilePage } from './profile.page';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
IonicModule,
|
||||||
|
CommonModule,
|
||||||
|
FormsModule,
|
||||||
|
ProfilePageRoutingModule
|
||||||
|
],
|
||||||
|
declarations: [ProfilePage]
|
||||||
|
})
|
||||||
|
export class ProfilePageModule {}
|
||||||
6
src/app/features/profile/profile.page.html
Normal file
6
src/app/features/profile/profile.page.html
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<ion-content [fullscreen]="true" class="pv-surface">
|
||||||
|
<div class="profile-container">
|
||||||
|
<h2 class="pv-headline-sm">Profilo</h2>
|
||||||
|
<p class="pv-label-md">Pagina profilo - placeholder</p>
|
||||||
|
</div>
|
||||||
|
</ion-content>
|
||||||
3
src/app/features/profile/profile.page.scss
Normal file
3
src/app/features/profile/profile.page.scss
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
.profile-container {
|
||||||
|
padding: 16px 20px;
|
||||||
|
}
|
||||||
11
src/app/features/profile/profile.page.ts
Normal file
11
src/app/features/profile/profile.page.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-profile',
|
||||||
|
templateUrl: 'profile.page.html',
|
||||||
|
styleUrls: ['profile.page.scss'],
|
||||||
|
standalone: false,
|
||||||
|
})
|
||||||
|
export class ProfilePage {
|
||||||
|
constructor() {}
|
||||||
|
}
|
||||||
13
src/app/features/search/search-routing.module.ts
Normal file
13
src/app/features/search/search-routing.module.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { RouterModule, Routes } from '@angular/router';
|
||||||
|
import { SearchPage } from './search.page';
|
||||||
|
|
||||||
|
const routes: Routes = [
|
||||||
|
{ path: '', component: SearchPage }
|
||||||
|
];
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [RouterModule.forChild(routes)],
|
||||||
|
exports: [RouterModule]
|
||||||
|
})
|
||||||
|
export class SearchPageRoutingModule {}
|
||||||
17
src/app/features/search/search.module.ts
Normal file
17
src/app/features/search/search.module.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { IonicModule } from '@ionic/angular';
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
|
import { SearchPageRoutingModule } from './search-routing.module';
|
||||||
|
import { SearchPage } from './search.page';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
IonicModule,
|
||||||
|
CommonModule,
|
||||||
|
FormsModule,
|
||||||
|
SearchPageRoutingModule
|
||||||
|
],
|
||||||
|
declarations: [SearchPage]
|
||||||
|
})
|
||||||
|
export class SearchPageModule {}
|
||||||
6
src/app/features/search/search.page.html
Normal file
6
src/app/features/search/search.page.html
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<ion-content [fullscreen]="true" class="pv-surface">
|
||||||
|
<div class="search-container">
|
||||||
|
<h2 class="pv-headline-sm">Piani</h2>
|
||||||
|
<p class="pv-label-md">Pagina piani alimentari - placeholder</p>
|
||||||
|
</div>
|
||||||
|
</ion-content>
|
||||||
3
src/app/features/search/search.page.scss
Normal file
3
src/app/features/search/search.page.scss
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
.search-container {
|
||||||
|
padding: 16px 20px;
|
||||||
|
}
|
||||||
11
src/app/features/search/search.page.ts
Normal file
11
src/app/features/search/search.page.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-search',
|
||||||
|
templateUrl: 'search.page.html',
|
||||||
|
styleUrls: ['search.page.scss'],
|
||||||
|
standalone: false,
|
||||||
|
})
|
||||||
|
export class SearchPage {
|
||||||
|
constructor() {}
|
||||||
|
}
|
||||||
13
src/app/features/stats/stats-routing.module.ts
Normal file
13
src/app/features/stats/stats-routing.module.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { RouterModule, Routes } from '@angular/router';
|
||||||
|
import { StatsPage } from './stats.page';
|
||||||
|
|
||||||
|
const routes: Routes = [
|
||||||
|
{ path: '', component: StatsPage }
|
||||||
|
];
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [RouterModule.forChild(routes)],
|
||||||
|
exports: [RouterModule]
|
||||||
|
})
|
||||||
|
export class StatsPageRoutingModule {}
|
||||||
17
src/app/features/stats/stats.module.ts
Normal file
17
src/app/features/stats/stats.module.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { IonicModule } from '@ionic/angular';
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
|
import { StatsPageRoutingModule } from './stats-routing.module';
|
||||||
|
import { StatsPage } from './stats.page';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
IonicModule,
|
||||||
|
CommonModule,
|
||||||
|
FormsModule,
|
||||||
|
StatsPageRoutingModule
|
||||||
|
],
|
||||||
|
declarations: [StatsPage]
|
||||||
|
})
|
||||||
|
export class StatsPageModule {}
|
||||||
6
src/app/features/stats/stats.page.html
Normal file
6
src/app/features/stats/stats.page.html
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<ion-content [fullscreen]="true" class="pv-surface">
|
||||||
|
<div class="stats-container">
|
||||||
|
<h2 class="pv-headline-sm">Statistiche</h2>
|
||||||
|
<p class="pv-label-md">Pagina statistiche - placeholder</p>
|
||||||
|
</div>
|
||||||
|
</ion-content>
|
||||||
3
src/app/features/stats/stats.page.scss
Normal file
3
src/app/features/stats/stats.page.scss
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
.stats-container {
|
||||||
|
padding: 16px 20px;
|
||||||
|
}
|
||||||
11
src/app/features/stats/stats.page.ts
Normal file
11
src/app/features/stats/stats.page.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-stats',
|
||||||
|
templateUrl: 'stats.page.html',
|
||||||
|
styleUrls: ['stats.page.scss'],
|
||||||
|
standalone: false,
|
||||||
|
})
|
||||||
|
export class StatsPage {
|
||||||
|
constructor() {}
|
||||||
|
}
|
||||||
43
src/app/tabs/tabs-routing.module.ts
Normal file
43
src/app/tabs/tabs-routing.module.ts
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { RouterModule, Routes } from '@angular/router';
|
||||||
|
import { TabsPage } from './tabs.page';
|
||||||
|
|
||||||
|
const routes: Routes = [
|
||||||
|
{
|
||||||
|
path: 'tabs',
|
||||||
|
component: TabsPage,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: 'home',
|
||||||
|
loadChildren: () => import('../features/home/home.module').then(m => m.HomePageModule)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'stats',
|
||||||
|
loadChildren: () => import('../features/stats/stats.module').then(m => m.StatsPageModule)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'plans',
|
||||||
|
loadChildren: () => import('../features/search/search.module').then(m => m.SearchPageModule)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'profile',
|
||||||
|
loadChildren: () => import('../features/profile/profile.module').then(m => m.ProfilePageModule)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
redirectTo: '/tabs/home',
|
||||||
|
pathMatch: 'full'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
redirectTo: '/tabs/home',
|
||||||
|
pathMatch: 'full'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [RouterModule.forChild(routes)],
|
||||||
|
})
|
||||||
|
export class TabsPageRoutingModule {}
|
||||||
19
src/app/tabs/tabs.module.ts
Normal file
19
src/app/tabs/tabs.module.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { IonicModule } from '@ionic/angular';
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
|
|
||||||
|
import { TabsPageRoutingModule } from './tabs-routing.module';
|
||||||
|
|
||||||
|
import { TabsPage } from './tabs.page';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
IonicModule,
|
||||||
|
CommonModule,
|
||||||
|
FormsModule,
|
||||||
|
TabsPageRoutingModule
|
||||||
|
],
|
||||||
|
declarations: [TabsPage]
|
||||||
|
})
|
||||||
|
export class TabsPageModule {}
|
||||||
25
src/app/tabs/tabs.page.html
Normal file
25
src/app/tabs/tabs.page.html
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<ion-tabs>
|
||||||
|
|
||||||
|
<ion-tab-bar slot="bottom">
|
||||||
|
<ion-tab-button tab="home" href="/tabs/home">
|
||||||
|
<ion-icon aria-hidden="true" name="book-outline"></ion-icon>
|
||||||
|
<ion-label>Journal</ion-label>
|
||||||
|
</ion-tab-button>
|
||||||
|
|
||||||
|
<ion-tab-button tab="stats" href="/tabs/stats">
|
||||||
|
<ion-icon aria-hidden="true" name="stats-chart-outline"></ion-icon>
|
||||||
|
<ion-label>Stats</ion-label>
|
||||||
|
</ion-tab-button>
|
||||||
|
|
||||||
|
<ion-tab-button tab="plans" href="/tabs/plans">
|
||||||
|
<ion-icon aria-hidden="true" name="calendar-outline"></ion-icon>
|
||||||
|
<ion-label>Plans</ion-label>
|
||||||
|
</ion-tab-button>
|
||||||
|
|
||||||
|
<ion-tab-button tab="profile" href="/tabs/profile">
|
||||||
|
<ion-icon aria-hidden="true" name="person-outline"></ion-icon>
|
||||||
|
<ion-label>Profile</ion-label>
|
||||||
|
</ion-tab-button>
|
||||||
|
</ion-tab-bar>
|
||||||
|
|
||||||
|
</ion-tabs>
|
||||||
6
src/app/tabs/tabs.page.scss
Normal file
6
src/app/tabs/tabs.page.scss
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
ion-tab-bar {
|
||||||
|
--background: var(--pv-surface-container-lowest, #ffffff);
|
||||||
|
--color: var(--pv-on-surface-variant, #757575);
|
||||||
|
--color-selected: var(--pv-primary, #006b1b);
|
||||||
|
border-top: none;
|
||||||
|
}
|
||||||
26
src/app/tabs/tabs.page.spec.ts
Normal file
26
src/app/tabs/tabs.page.spec.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { TabsPage } from './tabs.page';
|
||||||
|
|
||||||
|
describe('TabsPage', () => {
|
||||||
|
let component: TabsPage;
|
||||||
|
let fixture: ComponentFixture<TabsPage>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [TabsPage],
|
||||||
|
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||||
|
}).compileComponents();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(TabsPage);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
13
src/app/tabs/tabs.page.ts
Normal file
13
src/app/tabs/tabs.page.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-tabs',
|
||||||
|
templateUrl: 'tabs.page.html',
|
||||||
|
styleUrls: ['tabs.page.scss'],
|
||||||
|
standalone: false,
|
||||||
|
})
|
||||||
|
export class TabsPage {
|
||||||
|
|
||||||
|
constructor() {}
|
||||||
|
|
||||||
|
}
|
||||||
39
src/assets/data/food-dictionary.md
Normal file
39
src/assets/data/food-dictionary.md
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
# Food Dictionary — CalorieTracker
|
||||||
|
|
||||||
|
> This dictionary is included in the Claude AI system prompt for calorie estimation.
|
||||||
|
> Values are approximate and refer to common Italian foods.
|
||||||
|
|
||||||
|
## Format
|
||||||
|
Each entry: `food_name | portion | kcal`
|
||||||
|
|
||||||
|
## Entries
|
||||||
|
pasta al pomodoro | 100g | 160
|
||||||
|
pasta al pesto | 100g | 190
|
||||||
|
pasta alla carbonara | 100g | 200
|
||||||
|
risotto ai funghi | 100g | 170
|
||||||
|
pizza margherita | 1 fetta (150g) | 270
|
||||||
|
petto di pollo alla griglia | 100g | 165
|
||||||
|
bistecca di manzo | 100g | 250
|
||||||
|
salmone al forno | 100g | 208
|
||||||
|
insalata mista | porzione | 35
|
||||||
|
insalata caprese | porzione | 250
|
||||||
|
mozzarella | 100g | 280
|
||||||
|
parmigiano reggiano | 30g | 120
|
||||||
|
pane bianco | fetta (30g) | 80
|
||||||
|
pane integrale | fetta (30g) | 70
|
||||||
|
riso bianco | 100g cotto | 130
|
||||||
|
uova sode | 1 uovo | 78
|
||||||
|
prosciutto crudo | 50g | 130
|
||||||
|
bresaola | 50g | 75
|
||||||
|
olio extravergine | cucchiaio (10ml) | 90
|
||||||
|
frutta fresca mista | porzione (150g) | 75
|
||||||
|
yogurt bianco | vasetto (125g) | 65
|
||||||
|
latte intero | bicchiere (200ml) | 130
|
||||||
|
caffe espresso | tazzina | 2
|
||||||
|
cappuccino | tazza | 80
|
||||||
|
succo d'arancia | bicchiere (200ml) | 90
|
||||||
|
birra | bottiglia (330ml) | 140
|
||||||
|
vino rosso | bicchiere (150ml) | 125
|
||||||
|
tiramisù | porzione (120g) | 350
|
||||||
|
gelato | pallina (70g) | 130
|
||||||
|
cornetto | 1 pezzo | 230
|
||||||
BIN
src/assets/icon/favicon.png
Normal file
BIN
src/assets/icon/favicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 930 B |
1
src/assets/shapes.svg
Normal file
1
src/assets/shapes.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg width="350" height="140" xmlns="http://www.w3.org/2000/svg" style="background:#f6f7f9"><g fill="none" fill-rule="evenodd"><path fill="#F04141" style="mix-blend-mode:multiply" d="M61.905-34.23l96.194 54.51-66.982 54.512L22 34.887z"/><circle fill="#10DC60" style="mix-blend-mode:multiply" cx="155.5" cy="135.5" r="57.5"/><path fill="#3880FF" style="mix-blend-mode:multiply" d="M208.538 9.513l84.417 15.392L223.93 93.93z"/><path fill="#FFCE00" style="mix-blend-mode:multiply" d="M268.625 106.557l46.332-26.75 46.332 26.75v53.5l-46.332 26.75-46.332-26.75z"/><circle fill="#7044FF" style="mix-blend-mode:multiply" cx="299.5" cy="9.5" r="38.5"/><rect fill="#11D3EA" style="mix-blend-mode:multiply" transform="rotate(-60 148.47 37.886)" x="143.372" y="-7.056" width="10.196" height="89.884" rx="5.098"/><path d="M-25.389 74.253l84.86 8.107c5.498.525 9.53 5.407 9.004 10.905a10 10 0 0 1-.057.477l-12.36 85.671a10.002 10.002 0 0 1-11.634 8.42l-86.351-15.226c-5.44-.959-9.07-6.145-8.112-11.584l13.851-78.551a10 10 0 0 1 10.799-8.219z" fill="#7044FF" style="mix-blend-mode:multiply"/><circle fill="#0CD1E8" style="mix-blend-mode:multiply" cx="273.5" cy="106.5" r="20.5"/></g></svg>
|
||||||
|
After Width: | Height: | Size: 1.1 KiB |
16
src/environments/environment.example.ts
Normal file
16
src/environments/environment.example.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
// Copy this file to environment.ts and environment.prod.ts
|
||||||
|
// and fill in your Firebase config values.
|
||||||
|
// DO NOT commit environment.ts or environment.prod.ts!
|
||||||
|
export const environment = {
|
||||||
|
production: false,
|
||||||
|
firebase: {
|
||||||
|
apiKey: 'YOUR_API_KEY',
|
||||||
|
authDomain: 'YOUR_PROJECT.firebaseapp.com',
|
||||||
|
projectId: 'YOUR_PROJECT_ID',
|
||||||
|
storageBucket: 'YOUR_PROJECT.appspot.com',
|
||||||
|
messagingSenderId: 'YOUR_SENDER_ID',
|
||||||
|
appId: 'YOUR_APP_ID'
|
||||||
|
},
|
||||||
|
cloudFunctionUrl: 'http://localhost:5001/YOUR_PROJECT_ID/us-central1/estimateCalories',
|
||||||
|
defaultCalorieTarget: 2000
|
||||||
|
};
|
||||||
198
src/global.scss
Normal file
198
src/global.scss
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
/*
|
||||||
|
* Precision Vitality — Design System
|
||||||
|
* ===========================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Google Fonts: Lexend (data) + Plus Jakarta Sans (UI) */
|
||||||
|
@import url('https://fonts.googleapis.com/css2?family=Lexend:wght@400;700&family=Plus+Jakarta+Sans:wght@400;500;600&display=swap');
|
||||||
|
|
||||||
|
/* Core CSS required for Ionic components to work properly */
|
||||||
|
@import "@ionic/angular/css/core.css";
|
||||||
|
|
||||||
|
/* Basic CSS for apps built with Ionic */
|
||||||
|
@import "@ionic/angular/css/normalize.css";
|
||||||
|
@import "@ionic/angular/css/structure.css";
|
||||||
|
@import "@ionic/angular/css/typography.css";
|
||||||
|
@import "@ionic/angular/css/display.css";
|
||||||
|
|
||||||
|
/* Optional CSS utils */
|
||||||
|
@import "@ionic/angular/css/padding.css";
|
||||||
|
@import "@ionic/angular/css/float-elements.css";
|
||||||
|
@import "@ionic/angular/css/text-alignment.css";
|
||||||
|
@import "@ionic/angular/css/text-transformation.css";
|
||||||
|
@import "@ionic/angular/css/flex-utils.css";
|
||||||
|
|
||||||
|
/* =========================================== */
|
||||||
|
/* Design Tokens — Precision Vitality */
|
||||||
|
/* =========================================== */
|
||||||
|
|
||||||
|
:root {
|
||||||
|
/* Colors */
|
||||||
|
--pv-primary: #006b1b;
|
||||||
|
--pv-primary-dim: #005d16;
|
||||||
|
--pv-primary-fixed: #7ddc7f;
|
||||||
|
--pv-accent: #FF7043;
|
||||||
|
--pv-error: #F44336;
|
||||||
|
--pv-surface: #f6f6f6;
|
||||||
|
--pv-surface-container-low: #f0f1f1;
|
||||||
|
--pv-surface-container-lowest: #ffffff;
|
||||||
|
--pv-surface-container-highest: #e2e3e3;
|
||||||
|
--pv-on-surface: #2d2f2f;
|
||||||
|
--pv-on-surface-variant: #757575;
|
||||||
|
--pv-on-primary: #ffffff;
|
||||||
|
--pv-outline-variant: rgba(117, 117, 117, 0.15);
|
||||||
|
|
||||||
|
/* Typography */
|
||||||
|
--pv-font-data: 'Lexend', sans-serif;
|
||||||
|
--pv-font-ui: 'Plus Jakarta Sans', sans-serif;
|
||||||
|
|
||||||
|
/* Radii */
|
||||||
|
--pv-radius-xl: 1.5rem;
|
||||||
|
--pv-radius-lg: 1rem;
|
||||||
|
--pv-radius-sm: 0.5rem;
|
||||||
|
|
||||||
|
/* Shadows (only for floating elements) */
|
||||||
|
--pv-shadow-fab: 0px 12px 32px rgba(0, 107, 27, 0.15);
|
||||||
|
|
||||||
|
/* Ionic overrides */
|
||||||
|
--ion-background-color: var(--pv-surface);
|
||||||
|
--ion-text-color: var(--pv-on-surface);
|
||||||
|
--ion-font-family: var(--pv-font-ui);
|
||||||
|
--ion-color-primary: #006b1b;
|
||||||
|
--ion-color-primary-rgb: 0, 107, 27;
|
||||||
|
--ion-color-primary-contrast: #ffffff;
|
||||||
|
--ion-color-primary-contrast-rgb: 255, 255, 255;
|
||||||
|
--ion-color-primary-shade: #005d16;
|
||||||
|
--ion-color-primary-tint: #1a7a32;
|
||||||
|
--ion-toolbar-background: var(--pv-surface);
|
||||||
|
--ion-toolbar-color: var(--pv-on-surface);
|
||||||
|
--ion-tab-bar-background: var(--pv-surface-container-lowest);
|
||||||
|
--ion-card-background: var(--pv-surface-container-lowest);
|
||||||
|
--ion-item-background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Disable dark mode auto-detection */
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
:root {
|
||||||
|
--ion-background-color: var(--pv-surface);
|
||||||
|
--ion-text-color: var(--pv-on-surface);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =========================================== */
|
||||||
|
/* Global Typography */
|
||||||
|
/* =========================================== */
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: var(--pv-font-ui);
|
||||||
|
color: var(--pv-on-surface);
|
||||||
|
background: var(--pv-surface);
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Data/number typography class */
|
||||||
|
.pv-data {
|
||||||
|
font-family: var(--pv-font-data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Display large — hero kcal numbers */
|
||||||
|
.pv-display-lg {
|
||||||
|
font-family: var(--pv-font-data);
|
||||||
|
font-size: 3.5rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--pv-primary);
|
||||||
|
line-height: 1.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Section headlines */
|
||||||
|
.pv-headline-sm {
|
||||||
|
font-family: var(--pv-font-data);
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--pv-on-surface);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Labels (metadata) */
|
||||||
|
.pv-label-md {
|
||||||
|
font-family: var(--pv-font-ui);
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--pv-on-surface-variant);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =========================================== */
|
||||||
|
/* No-Line Rule — Remove all Ionic borders */
|
||||||
|
/* =========================================== */
|
||||||
|
|
||||||
|
ion-item {
|
||||||
|
--border-color: transparent;
|
||||||
|
--inner-border-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ion-list {
|
||||||
|
--ion-item-border-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
ion-card {
|
||||||
|
border-radius: var(--pv-radius-xl);
|
||||||
|
box-shadow: none;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ion-toolbar {
|
||||||
|
--border-width: 0 !important;
|
||||||
|
--border-color: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =========================================== */
|
||||||
|
/* Card System — Tonal Layering, No Shadows */
|
||||||
|
/* =========================================== */
|
||||||
|
|
||||||
|
.pv-card {
|
||||||
|
background: var(--pv-surface-container-lowest);
|
||||||
|
border-radius: var(--pv-radius-xl);
|
||||||
|
padding: 24px;
|
||||||
|
box-shadow: none;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pv-card-nested {
|
||||||
|
background: var(--pv-surface-container-low);
|
||||||
|
border-radius: var(--pv-radius-lg);
|
||||||
|
padding: 16px;
|
||||||
|
box-shadow: none;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =========================================== */
|
||||||
|
/* Progress Bar */
|
||||||
|
/* =========================================== */
|
||||||
|
|
||||||
|
.pv-progress-bar {
|
||||||
|
height: 12px;
|
||||||
|
border-radius: 6px;
|
||||||
|
background: var(--pv-surface-container-highest);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pv-progress-bar-fill {
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 6px;
|
||||||
|
background: linear-gradient(90deg, var(--pv-primary-fixed), var(--pv-primary));
|
||||||
|
transition: width 0.6s cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pv-progress-bar-fill--over {
|
||||||
|
background: linear-gradient(90deg, #FF7043, var(--pv-error));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =========================================== */
|
||||||
|
/* Utility Surface Class */
|
||||||
|
/* =========================================== */
|
||||||
|
|
||||||
|
.pv-surface {
|
||||||
|
--background: var(--pv-surface);
|
||||||
|
}
|
||||||
29
src/index.html
Normal file
29
src/index.html
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="it">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<title>Precision Vitality</title>
|
||||||
|
|
||||||
|
<base href="/" />
|
||||||
|
|
||||||
|
<meta name="color-scheme" content="light" />
|
||||||
|
<meta name="viewport" content="viewport-fit=cover, width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
||||||
|
<meta name="format-detection" content="telephone=no" />
|
||||||
|
<meta name="msapplication-tap-highlight" content="no" />
|
||||||
|
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
|
||||||
|
<link rel="icon" type="image/png" href="assets/icon/favicon.png" />
|
||||||
|
|
||||||
|
<!-- add to homescreen for ios -->
|
||||||
|
<meta name="mobile-web-app-capable" content="yes" />
|
||||||
|
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<app-root></app-root>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
6
src/main.ts
Normal file
6
src/main.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||||
|
|
||||||
|
import { AppModule } from './app/app.module';
|
||||||
|
|
||||||
|
platformBrowserDynamic().bootstrapModule(AppModule)
|
||||||
|
.catch(err => console.log(err));
|
||||||
55
src/polyfills.ts
Normal file
55
src/polyfills.ts
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
/**
|
||||||
|
* This file includes polyfills needed by Angular and is loaded before the app.
|
||||||
|
* You can add your own extra polyfills to this file.
|
||||||
|
*
|
||||||
|
* This file is divided into 2 sections:
|
||||||
|
* 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
|
||||||
|
* 2. Application imports. Files imported after ZoneJS that should be loaded before your main
|
||||||
|
* file.
|
||||||
|
*
|
||||||
|
* The current setup is for so-called "evergreen" browsers; the last versions of browsers that
|
||||||
|
* automatically update themselves. This includes recent versions of Safari, Chrome (including
|
||||||
|
* Opera), Edge on the desktop, and iOS and Chrome on mobile.
|
||||||
|
*
|
||||||
|
* Learn more in https://angular.io/guide/browser-support
|
||||||
|
*/
|
||||||
|
|
||||||
|
/***************************************************************************************************
|
||||||
|
* BROWSER POLYFILLS
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* By default, zone.js will patch all possible macroTask and DomEvents
|
||||||
|
* user can disable parts of macroTask/DomEvents patch by setting following flags
|
||||||
|
* because those flags need to be set before `zone.js` being loaded, and webpack
|
||||||
|
* will put import in the top of bundle, so user need to create a separate file
|
||||||
|
* in this directory (for example: zone-flags.ts), and put the following flags
|
||||||
|
* into that file, and then add the following code before importing zone.js.
|
||||||
|
* import './zone-flags';
|
||||||
|
*
|
||||||
|
* The flags allowed in zone-flags.ts are listed here.
|
||||||
|
*
|
||||||
|
* The following flags will work for all browsers.
|
||||||
|
*
|
||||||
|
* (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
|
||||||
|
* (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
|
||||||
|
* (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
|
||||||
|
*
|
||||||
|
* in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
|
||||||
|
* with the following flag, it will bypass `zone.js` patch for IE/Edge
|
||||||
|
*
|
||||||
|
* (window as any).__Zone_enable_cross_context_check = true;
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
import './zone-flags';
|
||||||
|
|
||||||
|
/***************************************************************************************************
|
||||||
|
* Zone JS is required by default for Angular itself.
|
||||||
|
*/
|
||||||
|
import 'zone.js'; // Included with Angular CLI.
|
||||||
|
|
||||||
|
|
||||||
|
/***************************************************************************************************
|
||||||
|
* APPLICATION IMPORTS
|
||||||
|
*/
|
||||||
14
src/test.ts
Normal file
14
src/test.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
|
||||||
|
|
||||||
|
import 'zone.js/testing';
|
||||||
|
import { getTestBed } from '@angular/core/testing';
|
||||||
|
import {
|
||||||
|
BrowserDynamicTestingModule,
|
||||||
|
platformBrowserDynamicTesting
|
||||||
|
} from '@angular/platform-browser-dynamic/testing';
|
||||||
|
|
||||||
|
// First, initialize the Angular testing environment.
|
||||||
|
getTestBed().initTestEnvironment(
|
||||||
|
BrowserDynamicTestingModule,
|
||||||
|
platformBrowserDynamicTesting(),
|
||||||
|
);
|
||||||
3
src/theme/variables.scss
Normal file
3
src/theme/variables.scss
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
// Precision Vitality — Theme Variables
|
||||||
|
// All design tokens are defined in global.scss
|
||||||
|
// This file is kept for Ionic theme overrides only
|
||||||
6
src/zone-flags.ts
Normal file
6
src/zone-flags.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
/**
|
||||||
|
* Prevents Angular change detection from
|
||||||
|
* running with certain Web Component callbacks
|
||||||
|
*/
|
||||||
|
// eslint-disable-next-line no-underscore-dangle
|
||||||
|
(window as any).__Zone_disable_customElements = true;
|
||||||
15
tsconfig.app.json
Normal file
15
tsconfig.app.json
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "./out-tsc/app",
|
||||||
|
"types": []
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"src/main.ts",
|
||||||
|
"src/polyfills.ts"
|
||||||
|
],
|
||||||
|
"include": [
|
||||||
|
"src/**/*.d.ts"
|
||||||
|
]
|
||||||
|
}
|
||||||
34
tsconfig.json
Normal file
34
tsconfig.json
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
||||||
|
{
|
||||||
|
"compileOnSave": false,
|
||||||
|
"compilerOptions": {
|
||||||
|
"baseUrl": "./",
|
||||||
|
"outDir": "./dist/out-tsc",
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"strict": true,
|
||||||
|
"noImplicitOverride": true,
|
||||||
|
"noPropertyAccessFromIndexSignature": true,
|
||||||
|
"noImplicitReturns": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"declaration": false,
|
||||||
|
"downlevelIteration": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"importHelpers": true,
|
||||||
|
"target": "es2022",
|
||||||
|
"module": "es2020",
|
||||||
|
"lib": [
|
||||||
|
"es2018",
|
||||||
|
"dom"
|
||||||
|
],
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"useDefineForClassFields": false
|
||||||
|
},
|
||||||
|
"angularCompilerOptions": {
|
||||||
|
"enableI18nLegacyMessageIdFormat": false,
|
||||||
|
"strictInjectionParameters": true,
|
||||||
|
"strictInputAccessModifiers": true,
|
||||||
|
"strictTemplates": true
|
||||||
|
}
|
||||||
|
}
|
||||||
18
tsconfig.spec.json
Normal file
18
tsconfig.spec.json
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "./out-tsc/spec",
|
||||||
|
"types": [
|
||||||
|
"jasmine"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"src/test.ts",
|
||||||
|
"src/polyfills.ts"
|
||||||
|
],
|
||||||
|
"include": [
|
||||||
|
"src/**/*.spec.ts",
|
||||||
|
"src/**/*.d.ts"
|
||||||
|
]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user