diff --git a/.all-contributorsrc b/.all-contributorsrc index 0079dd719..6f2f57401 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -29,6 +29,10 @@ "translation-ru": { "symbol": "🇷🇺", "description": "Translate in Russian" + }, + "translation-ch": { + "symbol": "🇨🇳", + "description": "Translate in Chinese" } }, "contributors": [ @@ -100,6 +104,34 @@ "challenge" ] }, + { + "login": "alcaidio", + "name": "Timothy Alcaide", + "avatar_url": "https://avatars.githubusercontent.com/u/17033036?v=4", + "profile": "https://twitter.com/alcaidio", + "contributions": [ + "challenge" + ] + }, + { + "login": "LMFinney", + "name": "Lance Finney", + "avatar_url": "https://avatars.githubusercontent.com/u/6683747?v=4", + "profile": "https://github.com/LMFinney", + "contributions": [ + "doc", + "challenge" + ] + }, + { + "login": "tsironis13", + "name": "Tsironis Ioannis", + "avatar_url": "https://avatars.githubusercontent.com/u/7561447?v=4", + "profile": "https://github.com/tsironis13", + "contributions": [ + "challenge" + ] + }, { "login": "alan-bio", "name": "Alan Dragicevich", @@ -240,6 +272,97 @@ "contributions": [ "translation-ru" ] + }, + { + "login": "vimulatus", + "name": "Vimulatus", + "avatar_url": "https://avatars.githubusercontent.com/u/63696128?v=4", + "profile": "https://github.com/vimulatus", + "contributions": [ + "doc" + ] + }, + { + "login": "alannelucq", + "name": "Arthur LANNELUCQ", + "avatar_url": "https://avatars.githubusercontent.com/u/44091408?v=4", + "profile": "https://github.com/alannelucq", + "contributions": [ + "translation-fr" + ] + }, + { + "login": "fixedmichal", + "name": "fixed_michal", + "avatar_url": "https://avatars.githubusercontent.com/u/26270192?v=4", + "profile": "https://github.com/fixedmichal", + "contributions": [ + "bug" + ] + }, + { + "login": "Tenessy", + "name": "Tenessy", + "avatar_url": "https://avatars.githubusercontent.com/u/65855673?v=4", + "profile": "https://github.com/Tenessy", + "contributions": [ + "bug" + ] + }, + { + "login": "EnochGao", + "name": "Enoch Gao", + "avatar_url": "https://avatars.githubusercontent.com/u/41459067?v=4", + "profile": "https://enochgao.github.io/", + "contributions": [ + "doc", + "translation-ch" + ] + }, + { + "login": "fpalmab", + "name": "Francisco Palma", + "avatar_url": "https://avatars.githubusercontent.com/u/7729812?v=4", + "profile": "https://github.com/fpalmab", + "contributions": [ + "bug" + ] + }, + { + "login": "michalgrzegorczyk-dev", + "name": "Michał Grzegorczyk", + "avatar_url": "https://avatars.githubusercontent.com/u/47832176?v=4", + "profile": "https://github.com/michalgrzegorczyk-dev", + "contributions": [ + "doc" + ] + }, + { + "login": "tamim36", + "name": "Tamim Arefin Anik", + "avatar_url": "https://avatars.githubusercontent.com/u/42251521?v=4", + "profile": "https://github.com/tamim36", + "contributions": [ + "bug" + ] + }, + { + "login": "WhoisBsa", + "name": "Matheus B.", + "avatar_url": "https://avatars.githubusercontent.com/u/36895235?v=4", + "profile": "https://github.com/WhoisBsa", + "contributions": [ + "bug" + ] + }, + { + "login": "StefH", + "name": "Stef Heyenrath", + "avatar_url": "https://avatars.githubusercontent.com/u/249938?v=4", + "profile": "https://sourcerer.io/stefh", + "contributions": [ + "doc" + ] } ], "contributorsPerLine": 7, diff --git a/.eslintrc.json b/.eslintrc.json index c222fb084..de9b234b6 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -6,6 +6,7 @@ { "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], "rules": { + "@angular-eslint/no-host-metadata-property": "off", "@nx/enforce-module-boundaries": [ "error", { @@ -18,24 +19,24 @@ } ] } - ], - "@angular-eslint/no-host-metadata-property": [ - "error", - { - "allowStatic": true - } ] } }, { "files": ["*.ts", "*.tsx"], "extends": ["plugin:@nx/typescript"], - "rules": {} + "rules": { + "@typescript-eslint/no-extra-semi": "error", + "no-extra-semi": "off" + } }, { "files": ["*.js", "*.jsx"], "extends": ["plugin:@nx/javascript"], - "rules": {} + "rules": { + "@typescript-eslint/no-extra-semi": "error", + "no-extra-semi": "off" + } }, { "files": ["*.spec.ts", "*.spec.tsx", "*.spec.js", "*.spec.jsx"], diff --git a/.github/funding.yml b/.github/funding.yml new file mode 100644 index 000000000..f1327048f --- /dev/null +++ b/.github/funding.yml @@ -0,0 +1 @@ +github: [tomalaforge] diff --git a/.github/github-action/action.yml b/.github/github-action/action.yml new file mode 100644 index 000000000..8019592af --- /dev/null +++ b/.github/github-action/action.yml @@ -0,0 +1,14 @@ +name: 'Hello World' +description: 'Greet someone and record the time' +inputs: + github_token: + description: A GitHub token. + required: false + default: ${{ github.token }} + repo: + description: The owner and repository name. + required: false + default: ${{ github.repository }} +runs: + using: 'node20' + main: 'index.js' diff --git a/.github/github-action/contributors.js b/.github/github-action/contributors.js new file mode 100644 index 000000000..9555e6633 --- /dev/null +++ b/.github/github-action/contributors.js @@ -0,0 +1,34 @@ +const contributors = [ + 'alcaidio', + 'svenson95', + 'jdegand', + 'DeveshChau', + 'stillst', + 'wandri', + 'webbomj', + 'kabrunko-dev', + 'Sanjar1304', + 'tsironis13', + 'EnochGao', +]; + +const sponsors = [ + 'ddotx', + 'LMFinney', + 'alannelucq', + 'SidV2', + 'fpalmab', + 'CivilEngeneer', + 'apalaio', + 'amosISA', + 'michalgrzegorczyk-dev', + 'zealotrahl', + 'DzoeL123', + 'allan1989', + 'pchessah', +]; + +module.exports = { + contributors, + sponsors, +}; diff --git a/.github/github-action/index.js b/.github/github-action/index.js new file mode 100644 index 000000000..a73e6c293 --- /dev/null +++ b/.github/github-action/index.js @@ -0,0 +1,45 @@ +const github = require('@actions/github'); +const core = require('@actions/core'); +const { contributors, sponsors } = require('./contributors'); + +async function run() { + try { + const title = github.context.payload.pull_request.title; + const labels = ['answer']; + + const match = title.match(/Answer(:?)\s*(\d+)/); + if (match) { + labels.push(String(parseInt(match[2], 10))); + } + + const actor = github.context.actor; + if (contributors.includes(actor)) { + labels.push('contributor'); + labels.push('to be reviewed'); + } + + if (sponsors.includes(actor)) { + labels.push('sponsor'); + labels.push('to be reviewed'); + } + + const githubToken = core.getInput('github_token'); + + const number = github.context.payload.pull_request.number; + + const octokit = github.getOctokit(githubToken); + await octokit.rest.issues.addLabels({ + labels, + owner: github.context.repo.owner, + repo: github.context.repo.repo, + issue_number: number, + }); + } catch (e) { + if (e instanceof Error) { + core.error(e); + core.setFailed(e.message); + } + } +} + +run(); diff --git a/.github/instructions/nx.instructions.md b/.github/instructions/nx.instructions.md new file mode 100644 index 000000000..ceaed9595 --- /dev/null +++ b/.github/instructions/nx.instructions.md @@ -0,0 +1,31 @@ +--- +applyTo: '**' +--- + +// This file is automatically generated by Nx Console + +You are in an nx workspace using Nx 20.6.4 and npm as the package manager. + +You have access to the Nx MCP server and the tools it provides. Use them. Follow these guidelines in order to best help the user: + +# General Guidelines + +- When answering questions, use the nx_workspace tool first to gain an understanding of the workspace architecture +- For questions around nx configuration, best practices or if you're unsure, use the nx_docs tool to get relevant, up-to-date docs!! Always use this instead of assuming things about nx configuration +- If the user needs help with an Nx configuration or project graph error, use the 'nx_workspace' tool to get any errors +- To help answer questions about the workspace structure or simply help with demonstrating how tasks depend on each other, use the 'nx_visualize_graph' tool + +# Generation Guidelines + +If the user wants to generate something, use the following flow: + +- learn about the nx workspace and any specifics the user needs by using the 'nx_workspace' tool and the 'nx_project_details' tool if applicable +- get the available generators using the 'nx_generators' tool +- decide which generator to use. If no generators seem relevant, check the 'nx_available_plugins' tool to see if the user could install a plugin to help them +- get generator details using the 'nx_generator_schema' tool +- you may use the 'nx_docs' tool to learn more about a specific generator or technology if you're unsure +- decide which options to provide in order to best complete the user's request. Don't make any assumptions and keep the options minimalistic +- open the generator UI using the 'nx_open_generate_ui' tool +- wait for the user to finish the generator +- read the generator log file using the 'nx_read_generator_log' tool +- use the information provided in the log file to answer the user's question or continue with what they were doing diff --git a/.github/workflows/close-inactive-pr.yml b/.github/workflows/close-inactive-pr.yml index d92c25cdb..d5e0e9c95 100644 --- a/.github/workflows/close-inactive-pr.yml +++ b/.github/workflows/close-inactive-pr.yml @@ -1,7 +1,7 @@ name: Close inactive issues on: schedule: - - cron: '20 1 * * *' + - cron: '0 0 * * *' jobs: close-issues: @@ -10,18 +10,19 @@ jobs: issues: write pull-requests: write steps: - - uses: actions/stale@v5 + - uses: actions/stale@v9 with: days-before-issue-stale: 20 days-before-issue-close: -1 stale-issue-label: 'stale' - stale-issue-message: 'This issue is stale because it has been open for 20 days with no activity.' + stale-issue-message: 'This issue is stale because it has been open for 15 days with no activity.' exempt-issue-labels: 'long-term' days-before-pr-stale: 20 days-before-pr-close: 7 stale-pr-label: 'stale' - stale-pr-message: 'This pull request is stale because it has been open for 20 days with no activity.' - close-pr-message: 'This pull request was closed because it has been inactive for 7 days since being marked as stale.' + stale-pr-message: 'This pull request is stale because it has been open for 15 days with no activity.' + close-pr-message: 'This pull request was closed because it has been inactive for 5 days since being marked as stale.' only-pr-labels: 'answer' - exempt-pr-labels: 'challenge-creation, long-term' + exempt-pr-labels: 'challenge-creation, long-term, to be reviewed' + remove-pr-stale-when-updated: true repo-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/label-issue-update.yml b/.github/workflows/label-issue-update.yml new file mode 100644 index 000000000..5f79b0568 --- /dev/null +++ b/.github/workflows/label-issue-update.yml @@ -0,0 +1,21 @@ +name: updates Labels + +on: + push: + branches-ignore: + - main + +jobs: + update_labels: + runs-on: ubuntu-latest + if: | + contains(github.event.pull_request.labels.*.name, 'sponsor') || + contains(github.event.pull_request.labels.*.name, 'contributor') + steps: + - name: checkout + uses: actions/checkout@v2 + + - name: Add labels + uses: actions-ecosystem/action-add-labels@v1 + with: + labels: to be reviewed/update diff --git a/.github/workflows/label-issue.yml b/.github/workflows/label-issue.yml index 5c621375d..a5fd40632 100644 --- a/.github/workflows/label-issue.yml +++ b/.github/workflows/label-issue.yml @@ -1,29 +1,37 @@ name: Add Labels on: - pull_request: + pull_request_target: types: [ opened, edited, synchronize ] jobs: - add_supporters: + check-title: runs-on: ubuntu-latest steps: - - name: checkout - uses: actions/checkout@v2 - - - name: add labels - uses: actions-ecosystem/action-add-labels@v1 - if: ${{ contains( fromJson('[ "tomalaforge", "alcaidio" , "svenson95", "jdegand", "DeveshChau", "stillst", "wandri", "webbomj", "kabrunko-dev", "Sanjar1304"]'), github.actor ) && startsWith(github.event.pull_request.title, 'Answer') }} - with: - labels: supporter - add_answers: + - name: Check PR title + env: + PR_TITLE: ${{ github.event.pull_request.title }} + run: | + echo "Checking PR Title: '$PR_TITLE'" + if [[ ! "$PR_TITLE" =~ ^Answer: ]]; then + echo "❌ PR title should start with 'Answer:[#challenge number]'" + echo "### ❌ PR title should start with 'Answer:[#challenge number]'" >> $GITHUB_STEP_SUMMARY + exit 1 + else + echo "✅ PR title format is correct." + echo "### ✅ PR title format is correct." >> $GITHUB_STEP_SUMMARY + fi + add_labels: runs-on: ubuntu-latest + if: ${{ startsWith(github.event.pull_request.title, 'Answer') }} steps: - name: checkout uses: actions/checkout@v2 - - name: add labels - uses: actions-ecosystem/action-add-labels@v1 - if: ${{ startsWith(github.event.pull_request.title, 'Answer') }} + - name: Install dependencies + run: npm i @actions/core @actions/github + + - name: Add labels + uses: ./.github/github-action/ with: - labels: answer + github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index 5472d6bdf..cad0abc01 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,6 @@ Thumbs.db TODO.md .nx/cache +.nx/workspace-data + +.cursorrules \ No newline at end of file diff --git a/.prettierignore b/.prettierignore index ab639d33d..a4684ea74 100644 --- a/.prettierignore +++ b/.prettierignore @@ -5,4 +5,5 @@ .angular -/.nx/cache \ No newline at end of file +/.nx/cache +/.nx/workspace-data \ No newline at end of file diff --git a/README.md b/README.md index b516e561b..2965ecc08 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ If you would like to propose a challenge, this project is open source, so feel f ## Challenges -Check [all 47 challenges](https://angular-challenges.vercel.app/) +Check [all 59 challenges](https://angular-challenges.vercel.app/) ## Contributors ✨ @@ -40,25 +40,42 @@ Check [all 47 challenges](https://angular-challenges.vercel.app/)
+ {{ col }} + | +|||
---|---|---|---|
{{ product.name }} | +{{ product.priceA | currency | async }} | +{{ product.priceB | currency | async }} | +{{ product.priceC | currency | async }} | +
{props.description}
+ ++ Lorem ipsum dolor sit amet consectetur adipisicing elit. Vitae + mollitia sequi accusantium, distinctio similique laudantium eveniet + quidem sit placeat possimus tempore dolorum inventore corporis atque + quae ad, nobis explicabo delectus. +
++ Lorem ipsum dolor sit amet consectetur adipisicing elit. Vitae + mollitia sequi accusantium, distinctio similique laudantium eveniet + quidem sit placeat possimus tempore dolorum inventore corporis atque + quae ad, nobis explicabo delectus. +
++ Lorem ipsum dolor sit amet consectetur adipisicing elit. Vitae + mollitia sequi accusantium, distinctio similique laudantium eveniet + quidem sit placeat possimus tempore dolorum inventore corporis atque + quae ad, nobis explicabo delectus. +
+dashboard for Admin works!
diff --git a/apps/angular/permissions/src/app/dashboard/manager.component.ts b/apps/angular/6-structural-directive/src/app/dashboard/manager.component.ts similarity index 100% rename from apps/angular/permissions/src/app/dashboard/manager.component.ts rename to apps/angular/6-structural-directive/src/app/dashboard/manager.component.ts diff --git a/apps/angular/permissions/src/app/information.component.ts b/apps/angular/6-structural-directive/src/app/information.component.ts similarity index 97% rename from apps/angular/permissions/src/app/information.component.ts rename to apps/angular/6-structural-directive/src/app/information.component.ts index e4adeb1b9..81b339520 100644 --- a/apps/angular/permissions/src/app/information.component.ts +++ b/apps/angular/6-structural-directive/src/app/information.component.ts @@ -4,7 +4,6 @@ import { UserStore } from './user.store'; @Component({ selector: 'app-information', - standalone: true, imports: [CommonModule], template: `- {{ col }} - | -|||
---|---|---|---|
{{ product.name }} | -{{ product.priceA | currency | async }} | -{{ product.priceB | currency | async }} | -{{ product.priceC | currency | async }} | -
- Title: - {{ photo.title }} -
-- Owner: - {{ photo.ownername }} -
-- Date: - {{ photo.datetaken | date }} -
-- Tags: - {{ photo.tags }} -
- - - `, - host: { - class: 'p-5 block', - }, -}) -export default class DetailComponent { - @RouterInput({ - required: true, - transform: (value: string) => JSON.parse(decodeURIComponent(value)), - }) - photo!: Photo; -} diff --git a/apps/angular/interop-rxjs-signal/src/app/list/photos.component.ts b/apps/angular/interop-rxjs-signal/src/app/list/photos.component.ts deleted file mode 100644 index 29dc0c3f5..000000000 --- a/apps/angular/interop-rxjs-signal/src/app/list/photos.component.ts +++ /dev/null @@ -1,119 +0,0 @@ -import { NgFor, NgIf } from '@angular/common'; -import { Component, OnInit, inject } from '@angular/core'; -import { FormControl, ReactiveFormsModule } from '@angular/forms'; -import { MatFormFieldModule } from '@angular/material/form-field'; -import { MatInputModule } from '@angular/material/input'; -import { MatProgressBarModule } from '@angular/material/progress-bar'; -import { RouterLinkWithHref } from '@angular/router'; -import { LetDirective } from '@ngrx/component'; -import { provideComponentStore } from '@ngrx/component-store'; -import { debounceTime, distinctUntilChanged, skipWhile, tap } from 'rxjs'; -import { Photo } from '../photo.model'; -import { PhotoStore } from './photos.store'; - -@Component({ - selector: 'app-photos', - standalone: true, - imports: [ - ReactiveFormsModule, - MatFormFieldModule, - MatProgressBarModule, - NgIf, - NgFor, - MatInputModule, - LetDirective, - RouterLinkWithHref, - ], - template: ` -- {props.description} -
- -- Lorem ipsum dolor sit amet consectetur adipisicing elit. Vitae - mollitia sequi accusantium, distinctio similique laudantium eveniet - quidem sit placeat possimus tempore dolorum inventore corporis atque - quae ad, nobis explicabo delectus. -
-- Lorem ipsum dolor sit amet consectetur adipisicing elit. Vitae - mollitia sequi accusantium, distinctio similique laudantium eveniet - quidem sit placeat possimus tempore dolorum inventore corporis atque - quae ad, nobis explicabo delectus. -
-- Lorem ipsum dolor sit amet consectetur adipisicing elit. Vitae - mollitia sequi accusantium, distinctio similique laudantium eveniet - quidem sit placeat possimus tempore dolorum inventore corporis atque - quae ad, nobis explicabo delectus. -
-Do you want to continue and lose them?
+ +Main teacher: {{ activity.teacher.name }}
- All teachers available for : {{ activity.type }} are -Performance is key!!
+ + + @if (loadList) { +{{ person.email }}
+Performance is key!!
- - -{{ person.email }}
-+ Title: + {{ photo.title }} +
++ Owner: + {{ photo.ownername }} +
++ Date: + {{ photo.datetaken | date }} +
++ Tags: + {{ photo.tags }} +
+ + + `, + host: { + class: 'p-5 block', + }, +}) +export default class DetailComponent { + @RouterInput({ + required: true, + transform: (value: string) => JSON.parse(decodeURIComponent(value)), + }) + photo!: Photo; +} diff --git a/apps/signal/30-interop-rxjs-signal/src/app/list/photos.component.ts b/apps/signal/30-interop-rxjs-signal/src/app/list/photos.component.ts new file mode 100644 index 000000000..7ba115027 --- /dev/null +++ b/apps/signal/30-interop-rxjs-signal/src/app/list/photos.component.ts @@ -0,0 +1,128 @@ +import { AsyncPipe } from '@angular/common'; +import { Component, inject, OnInit } from '@angular/core'; +import { FormControl, ReactiveFormsModule } from '@angular/forms'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatInputModule } from '@angular/material/input'; +import { MatProgressBarModule } from '@angular/material/progress-bar'; +import { RouterLinkWithHref } from '@angular/router'; +import { provideComponentStore } from '@ngrx/component-store'; +import { + debounceTime, + distinctUntilChanged, + Observable, + skipWhile, + tap, +} from 'rxjs'; +import { Photo } from '../photo.model'; +import { PhotoStore } from './photos.store'; + +@Component({ + selector: 'app-photos', + imports: [ + ReactiveFormsModule, + MatFormFieldModule, + MatProgressBarModule, + MatInputModule, + RouterLinkWithHref, + AsyncPipe, + ], + template: ` +MacBook
+1999,99 €
+Extras:
+ +{{ col }} | + } +
---|
Title is {{ title }}
+ `, + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class ResultComponent { + @Input() title = ''; +} + +@Component({ + selector: 'app-button', + standalone: true, + template: ` + + `, + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class ButtonComponent { + @Output() validate = new EventEmitter(); +} + +@Component({ + selector: 'app-error', + standalone: true, + template: ` +Title is required !!!
+ `, + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class ErrorComponent { + @Output() validate = new EventEmitter(); +} + +@Component({ + selector: 'app-child', + imports: [ + ResultComponent, + ButtonComponent, + InputComponent, + ErrorComponent, + NgIf, + ], + template: ` +Counter: {{ counter() }}
+ + + + `, + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class CounterComponent { + initialValue = input.requiredEnabled only if Slider 1 > 20
+Enabled only if Slider 1 > 20
-Counter: {{ counter }}
- - - -Title is {{ title }}
- `, - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class ResultComponent { - @Input() title = ''; -} - -@Component({ - selector: 'app-button', - standalone: true, - template: ` - - `, - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class ButtonComponent { - @Output() validate = new EventEmitter(); -} - -@Component({ - selector: 'app-error', - standalone: true, - template: ` -Title is required !!!
- `, - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class ErrorComponent { - @Output() validate = new EventEmitter(); -} - -@Component({ - selector: 'app-child', - standalone: true, - imports: [ - ResultComponent, - ButtonComponent, - InputComponent, - ErrorComponent, - NgIf, - ], - template: ` -FirstName | -{{ row.firstName }} | -LastName | -{{ row.lastName }} | -{{ row.email }} | -
---|
Selected Difficulty: {{ difficultyLabel() }}
+{{ directionLabel() }}
+Selected Difficulty: {{ difficultyLabel() }}
-{{ directionLabel() }}
-@import '@angular/cdk/overlay-prebuilt.css'
in styles.scss