├── .gitignore ├── ChatAppClient ├── .editorconfig ├── .gitignore ├── .vscode │ ├── extensions.json │ ├── launch.json │ └── tasks.json ├── README.md ├── angular.json ├── package-lock.json ├── package.json ├── src │ ├── app │ │ ├── app.component.css │ │ ├── app.component.html │ │ ├── app.component.ts │ │ ├── app.config.ts │ │ ├── app.routes.ts │ │ ├── auth.service.ts │ │ ├── components │ │ │ ├── home │ │ │ │ ├── home.component.css │ │ │ │ ├── home.component.html │ │ │ │ └── home.component.ts │ │ │ ├── login │ │ │ │ ├── login.component.css │ │ │ │ ├── login.component.html │ │ │ │ └── login.component.ts │ │ │ └── register │ │ │ │ ├── register.component.css │ │ │ │ ├── register.component.html │ │ │ │ └── register.component.ts │ │ └── models │ │ │ ├── chat.model.ts │ │ │ ├── register.model.ts │ │ │ └── user.model.ts │ ├── assets │ │ └── .gitkeep │ ├── favicon.ico │ ├── index.html │ ├── main.ts │ └── styles.css ├── tsconfig.app.json ├── tsconfig.json └── tsconfig.spec.json ├── ChatAppServer ├── ChatAppServer.WebAPI │ ├── ChatAppServer.WebAPI.csproj │ ├── ChatAppServer.WebAPI.http │ ├── Context │ │ └── ApplicationDbContext.cs │ ├── Controllers │ │ ├── AuthController.cs │ │ └── ChatsController.cs │ ├── Dtos │ │ ├── RegisterDto.cs │ │ └── SendMessageDto.cs │ ├── Hubs │ │ └── ChatHub.cs │ ├── Migrations │ │ ├── 20240430090127_mg1.Designer.cs │ │ ├── 20240430090127_mg1.cs │ │ ├── 20240430090342_mg2.Designer.cs │ │ ├── 20240430090342_mg2.cs │ │ └── ApplicationDbContextModelSnapshot.cs │ ├── Models │ │ ├── Chat.cs │ │ └── User.cs │ ├── Program.cs │ ├── appsettings.Development.json │ ├── appsettings.json │ └── wwwroot │ │ └── avatar │ │ ├── 03cf04de-6ecb-4e43-b0b1-f828c805c71b.png │ │ ├── 17beaeac-4323-4f77-90e6-15a3e7711a15.png │ │ ├── ca04e629-ebbf-4f81-acd8-1616864954a0.png │ │ └── e1eb1e28-0987-4c22-877a-39cb86f955bc.png └── ChatAppServer.sln └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore Visual Studio temporary files, build results, and 2 | # files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.rsuser 6 | *.suo 7 | *.user 8 | *.userosscache 9 | *.sln.docstates 10 | 11 | # User-specific files (MonoDevelop/Xamarin Studio) 12 | *.userprefs 13 | 14 | # Mono Auto Generated Files 15 | mono_crash.* 16 | 17 | # Build results 18 | [Dd]ebug/ 19 | [Dd]ebugPublic/ 20 | [Rr]elease/ 21 | [Rr]eleases/ 22 | x64/ 23 | x86/ 24 | [Aa][Rr][Mm]/ 25 | [Aa][Rr][Mm]64/ 26 | bld/ 27 | [Bb]in/ 28 | [Oo]bj/ 29 | [Ll]og/ 30 | [Ll]ogs/ 31 | 32 | # Visual Studio 2015/2017 cache/options directory 33 | .vs/ 34 | # Uncomment if you have tasks that create the project's static files in wwwroot 35 | #wwwroot/ 36 | 37 | # MSTest test Results 38 | [Tt]est[Rr]esult*/ 39 | [Bb]uild[Ll]og.* 40 | 41 | # NUnit 42 | *.VisualState.xml 43 | TestResult.xml 44 | nunit-*.xml 45 | 46 | # Build Results of an MSBuild Project 47 | *_i.c 48 | *_p.c 49 | *_h.h 50 | *.ilk 51 | *.meta 52 | *.obj 53 | *.iobj 54 | *.pch 55 | *.pdb 56 | *.ipdb 57 | *.pgc 58 | *.pgd 59 | *.rsp 60 | *.sbr 61 | *.tlb 62 | *.tli 63 | *.tlh 64 | *.tmp 65 | *.tmp_proj 66 | *.log 67 | *.vspscc 68 | *.vssscc 69 | *.user 70 | *.msscc 71 | *.scc 72 | 73 | # Chutzpah Test files 74 | _Chutzpah* 75 | 76 | # Visual C++ cache files 77 | ipch/ 78 | *.aps 79 | *.ncb 80 | *.sdf 81 | *.opensdf 82 | *.cachefile 83 | 84 | # Visual Studio profiler 85 | *.psess 86 | *.vsp 87 | *.vspx 88 | *.sap 89 | 90 | # Visual Studio Trace Files 91 | *.e2e 92 | 93 | # TFS 2012 Local Workspace 94 | $tf/ 95 | 96 | # Guidance Automation Toolkit 97 | *.gpState 98 | 99 | # ReSharper is a .NET coding add-in 100 | _ReSharper*/ 101 | *.[Rr]e[Ss]harper 102 | *.DotSettings.user 103 | 104 | # JustCode is a .NET coding add-in 105 | .JustCode 106 | 107 | # TeamCity 108 | _TeamCity* 109 | 110 | # DotCover is a Code Coverage Tool 111 | *.dotCover 112 | 113 | # AxoCover 114 | .axoCover/* 115 | !.axoCover/settings.json 116 | 117 | # Visual Studio code coverage results 118 | *.coverage 119 | *.coveragexml 120 | 121 | # NCrunch 122 | _NCrunch_* 123 | .*crunch*.local.xml 124 | nCrunchTemp_* 125 | 126 | # MightyMoose 127 | *.mm.* 128 | AutoTest.Net/ 129 | 130 | # BizTalk temporary files 131 | *.PDB 132 | *.pfx 133 | *.publishsettings 134 | 135 | # Web workbench (sass) 136 | .sass-cache/ 137 | 138 | # Installshield output folder 139 | [Ee]xpress/ 140 | # DocProject is a documentation generator add-in 141 | DocProject/buildhelp/ 142 | DocProject/Help/*.HxT 143 | DocProject/Help/*.HxC 144 | DocProject/Help/*.HHK 145 | DocProject/Help/*.HHP 146 | DocProject/HTMLHelp_Workshop/ 147 | 148 | # Click-Once directory 149 | publish/ 150 | 151 | # Publish Web Output 152 | *.Publish.xml 153 | *.pubxml 154 | *.publishproj 155 | 156 | # NuGet Packages Directory 157 | *.nupkg 158 | # The packages folder can be ignored because of Package Restore 159 | **/[Pp]ackages/* 160 | *.nuget 161 | 162 | # Windows Azure Build Output 163 | csx/ 164 | *.build.csdef 165 | 166 | # Windows Store app package directory 167 | AppPackages/ 168 | 169 | # Others 170 | sql/ 171 | *.Cache 172 | ClientBin/ 173 | [Ss]tyle[Cc]op.* 174 | ~$* 175 | *~ 176 | *.dbmdl 177 | *.dbproj.schemaview 178 | *.jfm 179 | *.log 180 | *.pgml 181 | *.mdf 182 | *.ldf 183 | mdbackup/ 184 | *.tscache 185 | *.tfstate 186 | *.tfstate.backup 187 | *.bak 188 | *.bacpac 189 | databasedevelopment.md 190 | *.mdf 191 | *.ldf 192 | *_Database*.* 193 | *.scc 194 | [node_modules] 195 | [node_modules/**] 196 | /history/* 197 | **/site 198 | **/properties 199 | *.epp 200 | *.ini 201 | *.swp 202 | *.ctp 203 | *.bak.* 204 | *.swx 205 | *.mdl 206 | *.svj 207 | *.geo 208 | *.dat 209 | *.swp 210 | *.bk -------------------------------------------------------------------------------- /ChatAppClient/.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.ts] 12 | quote_type = single 13 | 14 | [*.md] 15 | max_line_length = off 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /ChatAppClient/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # Compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | /bazel-out 8 | 9 | # Node 10 | /node_modules 11 | npm-debug.log 12 | yarn-error.log 13 | 14 | # IDEs and editors 15 | .idea/ 16 | .project 17 | .classpath 18 | .c9/ 19 | *.launch 20 | .settings/ 21 | *.sublime-workspace 22 | 23 | # Visual Studio Code 24 | .vscode/* 25 | !.vscode/settings.json 26 | !.vscode/tasks.json 27 | !.vscode/launch.json 28 | !.vscode/extensions.json 29 | .history/* 30 | 31 | # Miscellaneous 32 | /.angular/cache 33 | .sass-cache/ 34 | /connect.lock 35 | /coverage 36 | /libpeerconnection.log 37 | testem.log 38 | /typings 39 | 40 | # System files 41 | .DS_Store 42 | Thumbs.db 43 | -------------------------------------------------------------------------------- /ChatAppClient/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846 3 | "recommendations": ["angular.ng-template"] 4 | } 5 | -------------------------------------------------------------------------------- /ChatAppClient/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 3 | "version": "0.2.0", 4 | "configurations": [ 5 | { 6 | "name": "ng serve", 7 | "type": "chrome", 8 | "request": "launch", 9 | "preLaunchTask": "npm: start", 10 | "url": "http://localhost:4200/" 11 | }, 12 | { 13 | "name": "ng test", 14 | "type": "chrome", 15 | "request": "launch", 16 | "preLaunchTask": "npm: test", 17 | "url": "http://localhost:9876/debug.html" 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /ChatAppClient/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558 3 | "version": "2.0.0", 4 | "tasks": [ 5 | { 6 | "type": "npm", 7 | "script": "start", 8 | "isBackground": true, 9 | "problemMatcher": { 10 | "owner": "typescript", 11 | "pattern": "$tsc", 12 | "background": { 13 | "activeOnStart": true, 14 | "beginsPattern": { 15 | "regexp": "(.*?)" 16 | }, 17 | "endsPattern": { 18 | "regexp": "bundle generation complete" 19 | } 20 | } 21 | } 22 | }, 23 | { 24 | "type": "npm", 25 | "script": "test", 26 | "isBackground": true, 27 | "problemMatcher": { 28 | "owner": "typescript", 29 | "pattern": "$tsc", 30 | "background": { 31 | "activeOnStart": true, 32 | "beginsPattern": { 33 | "regexp": "(.*?)" 34 | }, 35 | "endsPattern": { 36 | "regexp": "bundle generation complete" 37 | } 38 | } 39 | } 40 | } 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /ChatAppClient/README.md: -------------------------------------------------------------------------------- 1 | # ChatAppClient 2 | 3 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 17.3.5. 4 | 5 | ## Development server 6 | 7 | Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files. 8 | 9 | ## Code scaffolding 10 | 11 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. 12 | 13 | ## Build 14 | 15 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. 16 | 17 | ## Running unit tests 18 | 19 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). 20 | 21 | ## Running end-to-end tests 22 | 23 | Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities. 24 | 25 | ## Further help 26 | 27 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page. 28 | -------------------------------------------------------------------------------- /ChatAppClient/angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "ChatAppClient": { 7 | "projectType": "application", 8 | "schematics": { 9 | "@schematics/angular:class": { 10 | "skipTests": true 11 | }, 12 | "@schematics/angular:component": { 13 | "skipTests": true 14 | }, 15 | "@schematics/angular:directive": { 16 | "skipTests": true 17 | }, 18 | "@schematics/angular:guard": { 19 | "skipTests": true 20 | }, 21 | "@schematics/angular:interceptor": { 22 | "skipTests": true 23 | }, 24 | "@schematics/angular:pipe": { 25 | "skipTests": true 26 | }, 27 | "@schematics/angular:resolver": { 28 | "skipTests": true 29 | }, 30 | "@schematics/angular:service": { 31 | "skipTests": true 32 | } 33 | }, 34 | "root": "", 35 | "sourceRoot": "src", 36 | "prefix": "app", 37 | "architect": { 38 | "build": { 39 | "builder": "@angular-devkit/build-angular:application", 40 | "options": { 41 | "outputPath": "dist/chat-app-client", 42 | "index": "src/index.html", 43 | "browser": "src/main.ts", 44 | "polyfills": [ 45 | "zone.js" 46 | ], 47 | "tsConfig": "tsconfig.app.json", 48 | "assets": [ 49 | "src/favicon.ico", 50 | "src/assets" 51 | ], 52 | "styles": [ 53 | "src/styles.css" 54 | ], 55 | "scripts": [] 56 | }, 57 | "configurations": { 58 | "production": { 59 | "budgets": [ 60 | { 61 | "type": "initial", 62 | "maximumWarning": "500kb", 63 | "maximumError": "1mb" 64 | }, 65 | { 66 | "type": "anyComponentStyle", 67 | "maximumWarning": "2kb", 68 | "maximumError": "4kb" 69 | } 70 | ], 71 | "outputHashing": "all" 72 | }, 73 | "development": { 74 | "optimization": false, 75 | "extractLicenses": false, 76 | "sourceMap": true 77 | } 78 | }, 79 | "defaultConfiguration": "production" 80 | }, 81 | "serve": { 82 | "builder": "@angular-devkit/build-angular:dev-server", 83 | "configurations": { 84 | "production": { 85 | "buildTarget": "ChatAppClient:build:production" 86 | }, 87 | "development": { 88 | "buildTarget": "ChatAppClient:build:development" 89 | } 90 | }, 91 | "defaultConfiguration": "development" 92 | }, 93 | "extract-i18n": { 94 | "builder": "@angular-devkit/build-angular:extract-i18n", 95 | "options": { 96 | "buildTarget": "ChatAppClient:build" 97 | } 98 | }, 99 | "test": { 100 | "builder": "@angular-devkit/build-angular:karma", 101 | "options": { 102 | "polyfills": [ 103 | "zone.js", 104 | "zone.js/testing" 105 | ], 106 | "tsConfig": "tsconfig.spec.json", 107 | "assets": [ 108 | "src/favicon.ico", 109 | "src/assets" 110 | ], 111 | "styles": [ 112 | "src/styles.css" 113 | ], 114 | "scripts": [] 115 | } 116 | } 117 | } 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /ChatAppClient/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chat-app-client", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "ng": "ng", 6 | "start": "ng serve", 7 | "build": "ng build", 8 | "watch": "ng build --watch --configuration development", 9 | "test": "ng test" 10 | }, 11 | "private": true, 12 | "dependencies": { 13 | "@angular/animations": "^17.3.0", 14 | "@angular/common": "^17.3.0", 15 | "@angular/compiler": "^17.3.0", 16 | "@angular/core": "^17.3.0", 17 | "@angular/forms": "^17.3.0", 18 | "@angular/platform-browser": "^17.3.0", 19 | "@angular/platform-browser-dynamic": "^17.3.0", 20 | "@angular/router": "^17.3.0", 21 | "@microsoft/signalr": "^8.0.0", 22 | "rxjs": "~7.8.0", 23 | "tslib": "^2.3.0", 24 | "zone.js": "~0.14.3" 25 | }, 26 | "devDependencies": { 27 | "@angular-devkit/build-angular": "^17.3.5", 28 | "@angular/cli": "^17.3.5", 29 | "@angular/compiler-cli": "^17.3.0", 30 | "@types/jasmine": "~5.1.0", 31 | "jasmine-core": "~5.1.0", 32 | "karma": "~6.4.0", 33 | "karma-chrome-launcher": "~3.2.0", 34 | "karma-coverage": "~2.2.0", 35 | "karma-jasmine": "~5.1.0", 36 | "karma-jasmine-html-reporter": "~2.1.0", 37 | "typescript": "~5.4.2" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /ChatAppClient/src/app/app.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanerSaydam/NET_Angular_SignalR_ile_ChatApp/855dac30d2f60f3a3116166c2ed29ad43f21081c/ChatAppClient/src/app/app.component.css -------------------------------------------------------------------------------- /ChatAppClient/src/app/app.component.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ChatAppClient/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { CommonModule } from '@angular/common'; 2 | import { Component } from '@angular/core'; 3 | import { RouterOutlet } from '@angular/router'; 4 | 5 | @Component({ 6 | selector: 'app-root', 7 | standalone: true, 8 | imports: [RouterOutlet], 9 | templateUrl: './app.component.html', 10 | styleUrl: './app.component.css' 11 | }) 12 | export class AppComponent { 13 | } 14 | -------------------------------------------------------------------------------- /ChatAppClient/src/app/app.config.ts: -------------------------------------------------------------------------------- 1 | import { ApplicationConfig } from '@angular/core'; 2 | import { provideRouter } from '@angular/router'; 3 | 4 | import { routes } from './app.routes'; 5 | import { provideHttpClient } from '@angular/common/http'; 6 | 7 | export const appConfig: ApplicationConfig = { 8 | providers: [provideRouter(routes), provideHttpClient()] 9 | }; 10 | -------------------------------------------------------------------------------- /ChatAppClient/src/app/app.routes.ts: -------------------------------------------------------------------------------- 1 | import { Routes } from '@angular/router'; 2 | import { HomeComponent } from './components/home/home.component'; 3 | import { LoginComponent } from './components/login/login.component'; 4 | import { RegisterComponent } from './components/register/register.component'; 5 | import { inject } from '@angular/core'; 6 | import { AuthService } from './auth.service'; 7 | 8 | export const routes: Routes = [ 9 | { 10 | path: "", 11 | component: HomeComponent, 12 | canActivate: [()=> inject(AuthService).isAuthenticated()] 13 | }, 14 | { 15 | path: "login", 16 | component: LoginComponent 17 | }, 18 | { 19 | path: "register", 20 | component: RegisterComponent 21 | } 22 | ]; 23 | -------------------------------------------------------------------------------- /ChatAppClient/src/app/auth.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Router } from '@angular/router'; 3 | 4 | @Injectable({ 5 | providedIn: 'root' 6 | }) 7 | export class AuthService { 8 | 9 | constructor(private router: Router) { } 10 | 11 | isAuthenticated(){ 12 | if(localStorage.getItem("accessToken")){ 13 | return true; 14 | } 15 | 16 | this.router.navigateByUrl("/login"); 17 | return false; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /ChatAppClient/src/app/components/home/home.component.css: -------------------------------------------------------------------------------- 1 | body{ 2 | background-color: #f4f7f6; 3 | margin-top:20px; 4 | } 5 | .card { 6 | background: #fff; 7 | transition: .5s; 8 | border: 0; 9 | margin-bottom: 30px; 10 | border-radius: .55rem; 11 | position: relative; 12 | width: 100%; 13 | box-shadow: 0 1px 2px 0 rgb(0 0 0 / 10%); 14 | } 15 | .chat-app .people-list { 16 | width: 280px; 17 | position: absolute; 18 | left: 0; 19 | top: 0; 20 | padding: 20px; 21 | z-index: 7 22 | } 23 | 24 | .chat-app .chat { 25 | margin-left: 280px; 26 | border-left: 1px solid #eaeaea 27 | } 28 | 29 | .people-list { 30 | -moz-transition: .5s; 31 | -o-transition: .5s; 32 | -webkit-transition: .5s; 33 | transition: .5s 34 | } 35 | 36 | .people-list .chat-list li { 37 | padding: 10px 15px; 38 | list-style: none; 39 | border-radius: 3px 40 | } 41 | 42 | .people-list .chat-list li:hover { 43 | background: #efefef; 44 | cursor: pointer 45 | } 46 | 47 | .people-list .chat-list li.active { 48 | background: #efefef 49 | } 50 | 51 | .people-list .chat-list li .name { 52 | font-size: 15px 53 | } 54 | 55 | .people-list .chat-list img { 56 | width: 45px; 57 | border-radius: 50% 58 | } 59 | 60 | .people-list img { 61 | float: left; 62 | border-radius: 50% 63 | } 64 | 65 | .people-list .about { 66 | float: left; 67 | padding-left: 8px 68 | } 69 | 70 | .people-list .status { 71 | color: #999; 72 | font-size: 13px 73 | } 74 | 75 | .chat .chat-header { 76 | padding: 15px 20px; 77 | border-bottom: 2px solid #f4f7f6 78 | } 79 | 80 | .chat .chat-header img { 81 | float: left; 82 | border-radius: 40px; 83 | width: 40px 84 | } 85 | 86 | .chat .chat-header .chat-about { 87 | float: left; 88 | padding-left: 10px 89 | } 90 | 91 | .chat .chat-history { 92 | padding: 20px; 93 | border-bottom: 2px solid #fff 94 | } 95 | 96 | .chat .chat-history ul { 97 | padding: 0 98 | } 99 | 100 | .chat .chat-history ul li { 101 | list-style: none; 102 | margin-bottom: 30px 103 | } 104 | 105 | .chat .chat-history ul li:last-child { 106 | margin-bottom: 0px 107 | } 108 | 109 | .chat .chat-history .message-data { 110 | margin-bottom: 15px 111 | } 112 | 113 | .chat .chat-history .message-data img { 114 | border-radius: 40px; 115 | width: 40px 116 | } 117 | 118 | .chat .chat-history .message-data-time { 119 | color: #434651; 120 | padding-left: 6px 121 | } 122 | 123 | .chat .chat-history .message { 124 | color: #444; 125 | padding: 18px 20px; 126 | line-height: 26px; 127 | font-size: 16px; 128 | border-radius: 7px; 129 | display: inline-block; 130 | position: relative 131 | } 132 | 133 | .chat .chat-history .message:after { 134 | bottom: 100%; 135 | left: 7%; 136 | border: solid transparent; 137 | content: " "; 138 | height: 0; 139 | width: 0; 140 | position: absolute; 141 | pointer-events: none; 142 | border-bottom-color: #fff; 143 | border-width: 10px; 144 | margin-left: -10px 145 | } 146 | 147 | .chat .chat-history .my-message { 148 | background: #efefef 149 | } 150 | 151 | .chat .chat-history .my-message:after { 152 | bottom: 100%; 153 | left: 30px; 154 | border: solid transparent; 155 | content: " "; 156 | height: 0; 157 | width: 0; 158 | position: absolute; 159 | pointer-events: none; 160 | border-bottom-color: #efefef; 161 | border-width: 10px; 162 | margin-left: -10px 163 | } 164 | 165 | .chat .chat-history .other-message { 166 | background: #e8f1f3; 167 | text-align: right 168 | } 169 | 170 | .chat .chat-history .other-message:after { 171 | border-bottom-color: #e8f1f3; 172 | left: 93% 173 | } 174 | 175 | .chat .chat-message { 176 | padding: 20px 177 | } 178 | 179 | .online, 180 | .offline, 181 | .me { 182 | margin-right: 2px; 183 | font-size: 8px; 184 | vertical-align: middle 185 | } 186 | 187 | .online { 188 | color: #86c541 189 | } 190 | 191 | .offline { 192 | color: #e47297 193 | } 194 | 195 | .me { 196 | color: #1d8ecd 197 | } 198 | 199 | .float-right { 200 | float: right 201 | } 202 | 203 | .clearfix:after { 204 | visibility: hidden; 205 | display: block; 206 | font-size: 0; 207 | content: " "; 208 | clear: both; 209 | height: 0 210 | } 211 | 212 | @media only screen and (max-width: 767px) { 213 | .chat-app .people-list { 214 | height: 465px; 215 | width: 100%; 216 | overflow-x: auto; 217 | background: #fff; 218 | left: -400px; 219 | display: none 220 | } 221 | .chat-app .people-list.open { 222 | left: 0 223 | } 224 | .chat-app .chat { 225 | margin: 0 226 | } 227 | .chat-app .chat .chat-header { 228 | border-radius: 0.55rem 0.55rem 0 0 229 | } 230 | .chat-app .chat-history { 231 | height: 300px; 232 | overflow-x: auto 233 | } 234 | } 235 | 236 | @media only screen and (min-width: 768px) and (max-width: 992px) { 237 | .chat-app .chat-list { 238 | height: 650px; 239 | overflow-x: auto 240 | } 241 | .chat-app .chat-history { 242 | height: 600px; 243 | overflow-x: auto 244 | } 245 | } 246 | 247 | @media only screen and (min-device-width: 768px) and (max-device-width: 1024px) and (orientation: landscape) and (-webkit-min-device-pixel-ratio: 1) { 248 | .chat-app .chat-list { 249 | height: 480px; 250 | overflow-x: auto 251 | } 252 | .chat-app .chat-history { 253 | height: calc(100vh - 350px); 254 | overflow-x: auto 255 | } 256 | } -------------------------------------------------------------------------------- /ChatAppClient/src/app/components/home/home.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

TS ChatAPP

5 | 8 |
9 |
10 |
11 |
12 |
13 | 14 | 15 |
16 |
    17 | @for(user of users; track user){ 18 |
  • 19 | avatar 20 |
    21 |
    {{user.name}}
    22 |
    {{user.status}}
    23 |
    24 |
  • 25 | } 26 |
27 |
28 | @if(selectedUserId){ 29 |
30 |
31 |
32 |
33 | 34 | avatar 35 | 36 |
37 |
{{selectedUser.name}}
38 | {{selectedUser.status}} 39 |
40 |
41 |
42 |
43 |
44 |
    45 | @for(chat of chats; track chat){ 46 | @if(selectedUserId != chat.userId){ 47 |
  • 48 |
    49 | {{chat.date}} 50 |
    51 |
    {{chat.message}}
    52 |
  • 53 | }@else { 54 |
  • 55 |
    56 | {{chat.date}} 57 |
    58 |
    {{chat.message}}
    59 |
  • 60 | } 61 | } 62 |
63 |
64 |
65 |
66 |
67 | 68 |
69 | 70 |
71 |
72 |
73 | }@else {} 74 | 75 |
76 |
77 |
78 |
-------------------------------------------------------------------------------- /ChatAppClient/src/app/components/home/home.component.ts: -------------------------------------------------------------------------------- 1 | import { CommonModule } from '@angular/common'; 2 | import { Component } from '@angular/core'; 3 | import { UserModel } from '../../models/user.model'; 4 | import { ChatModel } from '../../models/chat.model'; 5 | import { HttpClient } from '@angular/common/http'; 6 | import * as signalR from '@microsoft/signalr'; 7 | import { FormsModule } from '@angular/forms'; 8 | 9 | @Component({ 10 | selector: 'app-home', 11 | standalone: true, 12 | imports: [CommonModule,FormsModule], 13 | templateUrl: './home.component.html', 14 | styleUrl: './home.component.css' 15 | }) 16 | export class HomeComponent { 17 | users: UserModel[] = []; 18 | chats: ChatModel[] = []; 19 | selectedUserId: string = ""; 20 | selectedUser: UserModel = new UserModel(); 21 | user = new UserModel(); 22 | hub: signalR.HubConnection | undefined; 23 | message: string = ""; 24 | 25 | constructor( 26 | private http: HttpClient 27 | ){ 28 | this.user = JSON.parse(localStorage.getItem("accessToken") ?? ""); 29 | this.getUsers(); 30 | 31 | this.hub = new signalR.HubConnectionBuilder().withUrl("https://localhost:7123/chat-hub").build(); 32 | 33 | this.hub.start().then(()=> { 34 | console.log("Connection is started..."); 35 | 36 | this.hub?.invoke("Connect", this.user.id); 37 | 38 | this.hub?.on("Users", (res:UserModel) => { 39 | console.log(res); 40 | this.users.find(p=> p.id == res.id)!.status = res.status; 41 | }); 42 | 43 | this.hub?.on("Messages",(res:ChatModel)=> { 44 | console.log(res); 45 | 46 | if(this.selectedUserId == res.userId){ 47 | this.chats.push(res); 48 | } 49 | }) 50 | }) 51 | } 52 | 53 | getUsers(){ 54 | this.http.get("https://localhost:7123/api/Chats/GetUsers").subscribe(res=> { 55 | this.users = res.filter(p => p.id != this.user.id); 56 | }) 57 | } 58 | 59 | changeUser(user: UserModel){ 60 | this.selectedUserId = user.id; 61 | this.selectedUser = user; 62 | 63 | this.http.get(`https://localhost:7123/api/Chats/GetChats?userId=${this.user.id}&toUserId=${this.selectedUserId}`).subscribe((res:any)=>{ 64 | this.chats = res; 65 | }); 66 | } 67 | 68 | logout(){ 69 | localStorage.clear(); 70 | document.location.reload(); 71 | } 72 | 73 | sendMessage(){ 74 | const data ={ 75 | "userId": this.user.id, 76 | "toUserId": this.selectedUserId, 77 | "message": this.message 78 | } 79 | this.http.post("https://localhost:7123/api/Chats/SendMessage",data).subscribe( 80 | (res)=> { 81 | this.chats.push(res); 82 | this.message = ""; 83 | }); 84 | } 85 | 86 | } 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /ChatAppClient/src/app/components/login/login.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanerSaydam/NET_Angular_SignalR_ile_ChatApp/855dac30d2f60f3a3116166c2ed29ad43f21081c/ChatAppClient/src/app/components/login/login.component.css -------------------------------------------------------------------------------- /ChatAppClient/src/app/components/login/login.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |

Giriş Sayfası

6 |

Griş yapmak için bilgilerinizi doldurun

7 |
8 |
9 |
10 | 11 | 12 |
13 |
14 | 15 |
16 |
17 | Kayıt Ol 18 |
19 |
20 |
21 | 22 |
23 |
-------------------------------------------------------------------------------- /ChatAppClient/src/app/components/login/login.component.ts: -------------------------------------------------------------------------------- 1 | import { HttpClient } from '@angular/common/http'; 2 | import { Component } from '@angular/core'; 3 | import { FormsModule } from '@angular/forms'; 4 | import { Router } from '@angular/router'; 5 | 6 | @Component({ 7 | selector: 'app-login', 8 | standalone: true, 9 | imports: [FormsModule], 10 | templateUrl: './login.component.html', 11 | styleUrl: './login.component.css' 12 | }) 13 | export class LoginComponent { 14 | name: string = ""; 15 | 16 | constructor(private http: HttpClient, 17 | private router: Router 18 | ){ 19 | 20 | } 21 | login(){ 22 | this.http.get("https://localhost:7123/api/Auth/Login?name=" + this.name).subscribe(res=> { 23 | localStorage.setItem("accessToken", JSON.stringify(res)); 24 | this.router.navigateByUrl("/"); 25 | }); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /ChatAppClient/src/app/components/register/register.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanerSaydam/NET_Angular_SignalR_ile_ChatApp/855dac30d2f60f3a3116166c2ed29ad43f21081c/ChatAppClient/src/app/components/register/register.component.css -------------------------------------------------------------------------------- /ChatAppClient/src/app/components/register/register.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |

Giriş Sayfası

6 |

Griş yapmak için bilgilerinizi doldurun

7 |
8 |
9 |
10 | 11 | 12 |
13 | 14 |
15 | 16 | 17 |
18 |
19 | 20 |
21 |
22 |
23 | 24 |
25 |
-------------------------------------------------------------------------------- /ChatAppClient/src/app/components/register/register.component.ts: -------------------------------------------------------------------------------- 1 | import { HttpClient } from '@angular/common/http'; 2 | import { Component } from '@angular/core'; 3 | import { FormsModule } from '@angular/forms'; 4 | import { Router } from '@angular/router'; 5 | import { RegisterModel } from '../../models/register.model'; 6 | 7 | @Component({ 8 | selector: 'app-register', 9 | standalone: true, 10 | imports: [FormsModule], 11 | templateUrl: './register.component.html', 12 | styleUrl: './register.component.css' 13 | }) 14 | export class RegisterComponent { 15 | registerModel: RegisterModel = new RegisterModel(); 16 | 17 | constructor( 18 | private http: HttpClient, 19 | private router: Router 20 | ){} 21 | 22 | setImage(event:any){ 23 | this.registerModel.file = event.target.files[0]; 24 | } 25 | 26 | register(){ 27 | const formData = new FormData(); 28 | formData.append("name", this.registerModel.name); 29 | formData.append("file", this.registerModel.file, this.registerModel.file.name); 30 | 31 | this.http.post("https://localhost:7123/api/Auth/Register", formData).subscribe(res=> { 32 | localStorage.setItem("accessToken", JSON.stringify(res)); 33 | this.router.navigateByUrl("/"); 34 | }); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /ChatAppClient/src/app/models/chat.model.ts: -------------------------------------------------------------------------------- 1 | export class ChatModel{ 2 | userId: string = ""; 3 | toUserId: string = ""; 4 | date: string =""; 5 | message: string = ""; 6 | } 7 | -------------------------------------------------------------------------------- /ChatAppClient/src/app/models/register.model.ts: -------------------------------------------------------------------------------- 1 | export class RegisterModel{ 2 | name: string = ""; 3 | file: any; 4 | } -------------------------------------------------------------------------------- /ChatAppClient/src/app/models/user.model.ts: -------------------------------------------------------------------------------- 1 | export class UserModel{ 2 | id:string = ""; 3 | name: string = ""; 4 | status: string = ""; 5 | avatar: string = ""; 6 | } -------------------------------------------------------------------------------- /ChatAppClient/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanerSaydam/NET_Angular_SignalR_ile_ChatApp/855dac30d2f60f3a3116166c2ed29ad43f21081c/ChatAppClient/src/assets/.gitkeep -------------------------------------------------------------------------------- /ChatAppClient/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanerSaydam/NET_Angular_SignalR_ile_ChatApp/855dac30d2f60f3a3116166c2ed29ad43f21081c/ChatAppClient/src/favicon.ico -------------------------------------------------------------------------------- /ChatAppClient/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ChatAppClient 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /ChatAppClient/src/main.ts: -------------------------------------------------------------------------------- 1 | import { bootstrapApplication } from '@angular/platform-browser'; 2 | import { appConfig } from './app/app.config'; 3 | import { AppComponent } from './app/app.component'; 4 | 5 | bootstrapApplication(AppComponent, appConfig) 6 | .catch((err) => console.error(err)); 7 | -------------------------------------------------------------------------------- /ChatAppClient/src/styles.css: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | -------------------------------------------------------------------------------- /ChatAppClient/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "./out-tsc/app", 6 | "types": [] 7 | }, 8 | "files": [ 9 | "src/main.ts" 10 | ], 11 | "include": [ 12 | "src/**/*.d.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /ChatAppClient/tsconfig.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "compileOnSave": false, 4 | "compilerOptions": { 5 | "outDir": "./dist/out-tsc", 6 | "strict": true, 7 | "noImplicitOverride": true, 8 | "noPropertyAccessFromIndexSignature": true, 9 | "noImplicitReturns": true, 10 | "noFallthroughCasesInSwitch": true, 11 | "skipLibCheck": true, 12 | "esModuleInterop": true, 13 | "sourceMap": true, 14 | "declaration": false, 15 | "experimentalDecorators": true, 16 | "moduleResolution": "node", 17 | "importHelpers": true, 18 | "target": "ES2022", 19 | "module": "ES2022", 20 | "useDefineForClassFields": false, 21 | "lib": [ 22 | "ES2022", 23 | "dom" 24 | ] 25 | }, 26 | "angularCompilerOptions": { 27 | "enableI18nLegacyMessageIdFormat": false, 28 | "strictInjectionParameters": true, 29 | "strictInputAccessModifiers": true, 30 | "strictTemplates": true 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /ChatAppClient/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "./out-tsc/spec", 6 | "types": [ 7 | "jasmine" 8 | ] 9 | }, 10 | "include": [ 11 | "src/**/*.spec.ts", 12 | "src/**/*.d.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /ChatAppServer/ChatAppServer.WebAPI/ChatAppServer.WebAPI.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | all 15 | runtime; build; native; contentfiles; analyzers; buildtransitive 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /ChatAppServer/ChatAppServer.WebAPI/ChatAppServer.WebAPI.http: -------------------------------------------------------------------------------- 1 | @ChatAppServer.WebAPI_HostAddress = http://localhost:5245 2 | 3 | GET {{ChatAppServer.WebAPI_HostAddress}}/weatherforecast/ 4 | Accept: application/json 5 | 6 | ### 7 | -------------------------------------------------------------------------------- /ChatAppServer/ChatAppServer.WebAPI/Context/ApplicationDbContext.cs: -------------------------------------------------------------------------------- 1 | using ChatAppServer.WebAPI.Models; 2 | using Microsoft.EntityFrameworkCore; 3 | 4 | namespace ChatAppServer.WebAPI.Context; 5 | 6 | public sealed class ApplicationDbContext : DbContext 7 | { 8 | public ApplicationDbContext(DbContextOptions options) : base(options) 9 | { 10 | } 11 | 12 | public DbSet Users { get; set; } 13 | public DbSet Chats { get; set; } 14 | } 15 | -------------------------------------------------------------------------------- /ChatAppServer/ChatAppServer.WebAPI/Controllers/AuthController.cs: -------------------------------------------------------------------------------- 1 | using ChatAppServer.WebAPI.Context; 2 | using ChatAppServer.WebAPI.Dtos; 3 | using ChatAppServer.WebAPI.Models; 4 | using GenericFileService.Files; 5 | using Microsoft.AspNetCore.Mvc; 6 | using Microsoft.EntityFrameworkCore; 7 | 8 | namespace ChatAppServer.WebAPI.Controllers; 9 | [Route("api/[controller]/[action]")] 10 | [ApiController] 11 | public sealed class AuthController( 12 | ApplicationDbContext context) : ControllerBase 13 | { 14 | [HttpPost] 15 | public async Task Register([FromForm] RegisterDto request, CancellationToken cancellationToken) 16 | { 17 | bool isNameExists = await context.Users.AnyAsync(p => p.Name == request.Name, cancellationToken); 18 | 19 | if (isNameExists) 20 | { 21 | return BadRequest(new { Message = "Bu kullanıcı adı daha önce kullanılmış" }); 22 | } 23 | 24 | string avatar = FileService.FileSaveToServer(request.File, "wwwroot/avatar/"); 25 | 26 | User user = new() 27 | { 28 | Name = request.Name, 29 | Avatar = avatar 30 | }; 31 | 32 | await context.AddAsync(user, cancellationToken); 33 | await context.SaveChangesAsync(); 34 | 35 | return Ok(user); 36 | } 37 | 38 | [HttpGet] 39 | public async Task Login(string name, CancellationToken cancellationToken) 40 | { 41 | User? user = await context.Users.FirstOrDefaultAsync(p => p.Name == name, cancellationToken); 42 | 43 | if(user is null) 44 | { 45 | return BadRequest(new { Message = "Kullanıcı bulunamadı" }); 46 | } 47 | 48 | user.Status = "online"; 49 | 50 | await context.SaveChangesAsync(cancellationToken); 51 | 52 | return Ok(user); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /ChatAppServer/ChatAppServer.WebAPI/Controllers/ChatsController.cs: -------------------------------------------------------------------------------- 1 | using ChatAppServer.WebAPI.Context; 2 | using ChatAppServer.WebAPI.Dtos; 3 | using ChatAppServer.WebAPI.Hubs; 4 | using ChatAppServer.WebAPI.Models; 5 | using Microsoft.AspNetCore.Mvc; 6 | using Microsoft.AspNetCore.SignalR; 7 | using Microsoft.EntityFrameworkCore; 8 | 9 | namespace ChatAppServer.WebAPI.Controllers; 10 | [Route("api/[controller]/[action]")] 11 | [ApiController] 12 | public sealed class ChatsController( 13 | ApplicationDbContext context, 14 | IHubContext hubContext) : ControllerBase 15 | { 16 | [HttpGet] 17 | public async Task GetUsers() 18 | { 19 | List users = await context.Users.OrderBy(p => p.Name).ToListAsync(); 20 | return Ok(users); 21 | } 22 | 23 | [HttpGet] 24 | public async Task GetChats(Guid userId, Guid toUserId, CancellationToken cancellationToken) 25 | { 26 | List chats = 27 | await context 28 | .Chats 29 | .Where(p => 30 | p.UserId == userId && p.ToUserId == toUserId || 31 | p.ToUserId == userId && p.UserId == toUserId) 32 | .OrderBy(p=> p.Date) 33 | .ToListAsync(cancellationToken); 34 | 35 | return Ok(chats); 36 | } 37 | 38 | [HttpPost] 39 | public async Task SendMessage(SendMessageDto request, CancellationToken cancellationToken) 40 | { 41 | Chat chat = new() 42 | { 43 | UserId = request.UserId, 44 | ToUserId = request.ToUserId, 45 | Message = request.Message, 46 | Date = DateTime.Now 47 | }; 48 | 49 | await context.AddAsync(chat, cancellationToken); 50 | await context.SaveChangesAsync(cancellationToken); 51 | 52 | string connectionId = ChatHub.Users.First(p => p.Value == chat.ToUserId).Key; 53 | 54 | await hubContext.Clients.Client(connectionId).SendAsync("Messages", chat); 55 | 56 | return Ok(chat); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /ChatAppServer/ChatAppServer.WebAPI/Dtos/RegisterDto.cs: -------------------------------------------------------------------------------- 1 | namespace ChatAppServer.WebAPI.Dtos; 2 | 3 | public sealed record RegisterDto( 4 | string Name, 5 | IFormFile File); 6 | -------------------------------------------------------------------------------- /ChatAppServer/ChatAppServer.WebAPI/Dtos/SendMessageDto.cs: -------------------------------------------------------------------------------- 1 | namespace ChatAppServer.WebAPI.Dtos; 2 | 3 | public sealed record SendMessageDto( 4 | Guid UserId, 5 | Guid ToUserId, 6 | string Message); 7 | -------------------------------------------------------------------------------- /ChatAppServer/ChatAppServer.WebAPI/Hubs/ChatHub.cs: -------------------------------------------------------------------------------- 1 | using ChatAppServer.WebAPI.Context; 2 | using ChatAppServer.WebAPI.Models; 3 | using Microsoft.AspNetCore.SignalR; 4 | 5 | namespace ChatAppServer.WebAPI.Hubs; 6 | 7 | public sealed class ChatHub(ApplicationDbContext context) : Hub 8 | { 9 | public static Dictionary Users = new(); 10 | public async Task Connect(Guid userId) 11 | { 12 | Users.Add(Context.ConnectionId, userId); 13 | User? user = await context.Users.FindAsync(userId); 14 | if(user is not null) 15 | { 16 | user.Status = "online"; 17 | await context.SaveChangesAsync(); 18 | 19 | await Clients.All.SendAsync("Users", user); 20 | } 21 | } 22 | 23 | public override async Task OnDisconnectedAsync(Exception? exception) 24 | { 25 | Guid userId; 26 | Users.TryGetValue(Context.ConnectionId, out userId); 27 | Users.Remove(Context.ConnectionId); 28 | User? user = await context.Users.FindAsync(userId); 29 | if (user is not null) 30 | { 31 | user.Status = "offline"; 32 | await context.SaveChangesAsync(); 33 | 34 | await Clients.All.SendAsync("Users", user); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /ChatAppServer/ChatAppServer.WebAPI/Migrations/20240430090127_mg1.Designer.cs: -------------------------------------------------------------------------------- 1 | // 2 | using ChatAppServer.WebAPI.Context; 3 | using Microsoft.EntityFrameworkCore; 4 | using Microsoft.EntityFrameworkCore.Infrastructure; 5 | using Microsoft.EntityFrameworkCore.Metadata; 6 | using Microsoft.EntityFrameworkCore.Migrations; 7 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion; 8 | 9 | #nullable disable 10 | 11 | namespace ChatAppServer.WebAPI.Migrations 12 | { 13 | [DbContext(typeof(ApplicationDbContext))] 14 | [Migration("20240430090127_mg1")] 15 | partial class mg1 16 | { 17 | /// 18 | protected override void BuildTargetModel(ModelBuilder modelBuilder) 19 | { 20 | #pragma warning disable 612, 618 21 | modelBuilder 22 | .HasAnnotation("ProductVersion", "8.0.4") 23 | .HasAnnotation("Relational:MaxIdentifierLength", 128); 24 | 25 | SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); 26 | #pragma warning restore 612, 618 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /ChatAppServer/ChatAppServer.WebAPI/Migrations/20240430090127_mg1.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Migrations; 2 | 3 | #nullable disable 4 | 5 | namespace ChatAppServer.WebAPI.Migrations 6 | { 7 | /// 8 | public partial class mg1 : Migration 9 | { 10 | /// 11 | protected override void Up(MigrationBuilder migrationBuilder) 12 | { 13 | 14 | } 15 | 16 | /// 17 | protected override void Down(MigrationBuilder migrationBuilder) 18 | { 19 | 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /ChatAppServer/ChatAppServer.WebAPI/Migrations/20240430090342_mg2.Designer.cs: -------------------------------------------------------------------------------- 1 | // 2 | using System; 3 | using ChatAppServer.WebAPI.Context; 4 | using Microsoft.EntityFrameworkCore; 5 | using Microsoft.EntityFrameworkCore.Infrastructure; 6 | using Microsoft.EntityFrameworkCore.Metadata; 7 | using Microsoft.EntityFrameworkCore.Migrations; 8 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion; 9 | 10 | #nullable disable 11 | 12 | namespace ChatAppServer.WebAPI.Migrations 13 | { 14 | [DbContext(typeof(ApplicationDbContext))] 15 | [Migration("20240430090342_mg2")] 16 | partial class mg2 17 | { 18 | /// 19 | protected override void BuildTargetModel(ModelBuilder modelBuilder) 20 | { 21 | #pragma warning disable 612, 618 22 | modelBuilder 23 | .HasAnnotation("ProductVersion", "8.0.4") 24 | .HasAnnotation("Relational:MaxIdentifierLength", 128); 25 | 26 | SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); 27 | 28 | modelBuilder.Entity("ChatAppServer.WebAPI.Models.Chat", b => 29 | { 30 | b.Property("Id") 31 | .ValueGeneratedOnAdd() 32 | .HasColumnType("uniqueidentifier"); 33 | 34 | b.Property("Date") 35 | .HasColumnType("datetime2"); 36 | 37 | b.Property("Message") 38 | .IsRequired() 39 | .HasColumnType("nvarchar(max)"); 40 | 41 | b.Property("ToUserId") 42 | .HasColumnType("uniqueidentifier"); 43 | 44 | b.Property("UserId") 45 | .HasColumnType("uniqueidentifier"); 46 | 47 | b.HasKey("Id"); 48 | 49 | b.ToTable("Chats"); 50 | }); 51 | 52 | modelBuilder.Entity("ChatAppServer.WebAPI.Models.User", b => 53 | { 54 | b.Property("Id") 55 | .ValueGeneratedOnAdd() 56 | .HasColumnType("uniqueidentifier"); 57 | 58 | b.Property("Avatar") 59 | .IsRequired() 60 | .HasColumnType("nvarchar(max)"); 61 | 62 | b.Property("Name") 63 | .IsRequired() 64 | .HasColumnType("nvarchar(max)"); 65 | 66 | b.Property("Status") 67 | .IsRequired() 68 | .HasColumnType("nvarchar(max)"); 69 | 70 | b.HasKey("Id"); 71 | 72 | b.ToTable("Users"); 73 | }); 74 | #pragma warning restore 612, 618 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /ChatAppServer/ChatAppServer.WebAPI/Migrations/20240430090342_mg2.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.EntityFrameworkCore.Migrations; 3 | 4 | #nullable disable 5 | 6 | namespace ChatAppServer.WebAPI.Migrations 7 | { 8 | /// 9 | public partial class mg2 : Migration 10 | { 11 | /// 12 | protected override void Up(MigrationBuilder migrationBuilder) 13 | { 14 | migrationBuilder.CreateTable( 15 | name: "Chats", 16 | columns: table => new 17 | { 18 | Id = table.Column(type: "uniqueidentifier", nullable: false), 19 | UserId = table.Column(type: "uniqueidentifier", nullable: false), 20 | ToUserId = table.Column(type: "uniqueidentifier", nullable: false), 21 | Message = table.Column(type: "nvarchar(max)", nullable: false), 22 | Date = table.Column(type: "datetime2", nullable: false) 23 | }, 24 | constraints: table => 25 | { 26 | table.PrimaryKey("PK_Chats", x => x.Id); 27 | }); 28 | 29 | migrationBuilder.CreateTable( 30 | name: "Users", 31 | columns: table => new 32 | { 33 | Id = table.Column(type: "uniqueidentifier", nullable: false), 34 | Name = table.Column(type: "nvarchar(max)", nullable: false), 35 | Avatar = table.Column(type: "nvarchar(max)", nullable: false), 36 | Status = table.Column(type: "nvarchar(max)", nullable: false) 37 | }, 38 | constraints: table => 39 | { 40 | table.PrimaryKey("PK_Users", x => x.Id); 41 | }); 42 | } 43 | 44 | /// 45 | protected override void Down(MigrationBuilder migrationBuilder) 46 | { 47 | migrationBuilder.DropTable( 48 | name: "Chats"); 49 | 50 | migrationBuilder.DropTable( 51 | name: "Users"); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /ChatAppServer/ChatAppServer.WebAPI/Migrations/ApplicationDbContextModelSnapshot.cs: -------------------------------------------------------------------------------- 1 | // 2 | using System; 3 | using ChatAppServer.WebAPI.Context; 4 | using Microsoft.EntityFrameworkCore; 5 | using Microsoft.EntityFrameworkCore.Infrastructure; 6 | using Microsoft.EntityFrameworkCore.Metadata; 7 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion; 8 | 9 | #nullable disable 10 | 11 | namespace ChatAppServer.WebAPI.Migrations 12 | { 13 | [DbContext(typeof(ApplicationDbContext))] 14 | partial class ApplicationDbContextModelSnapshot : ModelSnapshot 15 | { 16 | protected override void BuildModel(ModelBuilder modelBuilder) 17 | { 18 | #pragma warning disable 612, 618 19 | modelBuilder 20 | .HasAnnotation("ProductVersion", "8.0.4") 21 | .HasAnnotation("Relational:MaxIdentifierLength", 128); 22 | 23 | SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); 24 | 25 | modelBuilder.Entity("ChatAppServer.WebAPI.Models.Chat", b => 26 | { 27 | b.Property("Id") 28 | .ValueGeneratedOnAdd() 29 | .HasColumnType("uniqueidentifier"); 30 | 31 | b.Property("Date") 32 | .HasColumnType("datetime2"); 33 | 34 | b.Property("Message") 35 | .IsRequired() 36 | .HasColumnType("nvarchar(max)"); 37 | 38 | b.Property("ToUserId") 39 | .HasColumnType("uniqueidentifier"); 40 | 41 | b.Property("UserId") 42 | .HasColumnType("uniqueidentifier"); 43 | 44 | b.HasKey("Id"); 45 | 46 | b.ToTable("Chats"); 47 | }); 48 | 49 | modelBuilder.Entity("ChatAppServer.WebAPI.Models.User", b => 50 | { 51 | b.Property("Id") 52 | .ValueGeneratedOnAdd() 53 | .HasColumnType("uniqueidentifier"); 54 | 55 | b.Property("Avatar") 56 | .IsRequired() 57 | .HasColumnType("nvarchar(max)"); 58 | 59 | b.Property("Name") 60 | .IsRequired() 61 | .HasColumnType("nvarchar(max)"); 62 | 63 | b.Property("Status") 64 | .IsRequired() 65 | .HasColumnType("nvarchar(max)"); 66 | 67 | b.HasKey("Id"); 68 | 69 | b.ToTable("Users"); 70 | }); 71 | #pragma warning restore 612, 618 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /ChatAppServer/ChatAppServer.WebAPI/Models/Chat.cs: -------------------------------------------------------------------------------- 1 | namespace ChatAppServer.WebAPI.Models; 2 | 3 | public sealed class Chat 4 | { 5 | public Chat() 6 | { 7 | Id = Guid.NewGuid(); 8 | } 9 | public Guid Id { get; set; } 10 | public Guid UserId { get; set; } 11 | public Guid ToUserId { get; set; } 12 | public string Message { get; set; } = string.Empty; 13 | public DateTime Date { get; set; } 14 | } 15 | -------------------------------------------------------------------------------- /ChatAppServer/ChatAppServer.WebAPI/Models/User.cs: -------------------------------------------------------------------------------- 1 | namespace ChatAppServer.WebAPI.Models; 2 | 3 | public sealed class User 4 | { 5 | public User() 6 | { 7 | Id = Guid.NewGuid(); 8 | } 9 | public Guid Id { get; set; } 10 | public string Name { get; set; } = string.Empty; 11 | public string Avatar { get; set; } = string.Empty; 12 | public string Status { get; set; } = string.Empty; 13 | } 14 | -------------------------------------------------------------------------------- /ChatAppServer/ChatAppServer.WebAPI/Program.cs: -------------------------------------------------------------------------------- 1 | using ChatAppServer.WebAPI.Context; 2 | using ChatAppServer.WebAPI.Hubs; 3 | using DefaultCorsPolicyNugetPackage; 4 | using Microsoft.EntityFrameworkCore; 5 | 6 | var builder = WebApplication.CreateBuilder(args); 7 | 8 | builder.Services.AddDefaultCors(); 9 | 10 | builder.Services.AddDbContext(options => options.UseSqlServer( 11 | builder.Configuration.GetConnectionString("SqlServer"))); 12 | builder.Services.AddControllers(); 13 | builder.Services.AddEndpointsApiExplorer(); 14 | builder.Services.AddSwaggerGen(); 15 | 16 | builder.Services.AddSignalR(); 17 | 18 | var app = builder.Build(); 19 | 20 | if (app.Environment.IsDevelopment()) 21 | { 22 | app.UseSwagger(); 23 | app.UseSwaggerUI(); 24 | } 25 | 26 | app.UseStaticFiles(); 27 | 28 | app.UseHttpsRedirection(); 29 | 30 | app.UseCors(); 31 | 32 | app.UseAuthorization(); 33 | 34 | app.MapControllers(); 35 | 36 | app.MapHub("/chat-hub"); 37 | app.Run(); 38 | -------------------------------------------------------------------------------- /ChatAppServer/ChatAppServer.WebAPI/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /ChatAppServer/ChatAppServer.WebAPI/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "AllowedHosts": "*", 9 | "ConnectionStrings": { 10 | "SqlServer": "Data Source=TANER\\SQLEXPRESS;Initial Catalog=ChatAppDb;Integrated Security=True;Connect Timeout=30;Encrypt=True;Trust Server Certificate=True;Application Intent=ReadWrite;Multi Subnet Failover=False" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /ChatAppServer/ChatAppServer.WebAPI/wwwroot/avatar/03cf04de-6ecb-4e43-b0b1-f828c805c71b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanerSaydam/NET_Angular_SignalR_ile_ChatApp/855dac30d2f60f3a3116166c2ed29ad43f21081c/ChatAppServer/ChatAppServer.WebAPI/wwwroot/avatar/03cf04de-6ecb-4e43-b0b1-f828c805c71b.png -------------------------------------------------------------------------------- /ChatAppServer/ChatAppServer.WebAPI/wwwroot/avatar/17beaeac-4323-4f77-90e6-15a3e7711a15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanerSaydam/NET_Angular_SignalR_ile_ChatApp/855dac30d2f60f3a3116166c2ed29ad43f21081c/ChatAppServer/ChatAppServer.WebAPI/wwwroot/avatar/17beaeac-4323-4f77-90e6-15a3e7711a15.png -------------------------------------------------------------------------------- /ChatAppServer/ChatAppServer.WebAPI/wwwroot/avatar/ca04e629-ebbf-4f81-acd8-1616864954a0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanerSaydam/NET_Angular_SignalR_ile_ChatApp/855dac30d2f60f3a3116166c2ed29ad43f21081c/ChatAppServer/ChatAppServer.WebAPI/wwwroot/avatar/ca04e629-ebbf-4f81-acd8-1616864954a0.png -------------------------------------------------------------------------------- /ChatAppServer/ChatAppServer.WebAPI/wwwroot/avatar/e1eb1e28-0987-4c22-877a-39cb86f955bc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanerSaydam/NET_Angular_SignalR_ile_ChatApp/855dac30d2f60f3a3116166c2ed29ad43f21081c/ChatAppServer/ChatAppServer.WebAPI/wwwroot/avatar/e1eb1e28-0987-4c22-877a-39cb86f955bc.png -------------------------------------------------------------------------------- /ChatAppServer/ChatAppServer.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.9.34728.123 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChatAppServer.WebAPI", "ChatAppServer.WebAPI\ChatAppServer.WebAPI.csproj", "{33DA9B9A-E4D0-4E05-9E44-12960192A42F}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {33DA9B9A-E4D0-4E05-9E44-12960192A42F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {33DA9B9A-E4D0-4E05-9E44-12960192A42F}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {33DA9B9A-E4D0-4E05-9E44-12960192A42F}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {33DA9B9A-E4D0-4E05-9E44-12960192A42F}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {C0F66CAB-DCE9-4836-AD34-97CD1997AD2E} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # .NET, Angular ve SignalR ile Chat Uygulaması Eğitimi 2 | Arkadaşlar merhaba! 3 | 4 | Bu eğitim ile SignalR kullanarak canlı chat yapmak isteyenlerin örnek alabileceği bir proje hazırladım. 5 | 6 | Eğitim başında kopyala yapıştır ile aldığım dizayn kodları aşağıdadır. 7 | 8 | Repoyu yıldızlarayak destek vermeyi unutmayın 9 | 10 | - **Youtube videosu** 11 | 12 | 13 | İyi eğitimler. 14 | 15 | 16 | - **HTML** 17 | ```html 18 |
19 |
20 |
21 |

TS ChatAPP

22 |
23 |
24 |
25 |
26 |
27 | 28 | 29 |
30 |
    31 | @for(user of users; track user){ 32 |
  • 33 | avatar 34 |
    35 |
    {{user.name}}
    36 |
    {{user.status}}
    37 |
    38 |
  • 39 | } 40 |
41 |
42 | @if(selectedUserId){ 43 |
44 |
45 |
46 |
47 | 48 | avatar 49 | 50 |
51 |
{{selectedUser.name}}
52 | {{selectedUser.status}} 53 |
54 |
55 |
56 |
57 |
58 |
    59 | @for(chat of chats; track chat){ 60 | @if(selectedUserId != chat.userId){ 61 |
  • 62 |
    63 | {{chat.date}} 64 |
    65 |
    {{chat.message}}
    66 |
  • 67 | }@else { 68 |
  • 69 |
    70 | {{chat.date}} 71 |
    72 |
    {{chat.message}}
    73 |
  • 74 | } 75 | } 76 |
77 |
78 |
79 |
80 |
81 | 82 |
83 | 84 |
85 |
86 |
87 | }@else { 88 |
89 |
90 |
91 |
92 | 93 | avatar 94 | 95 |
96 |
Aiden Chavez
97 | Last seen: 2 hours ago 98 |
99 |
100 |
101 |
102 |
103 |
    104 |
  • 105 |
    106 | 10:10 AM, Today 107 | avatar 108 |
    109 |
    Hi Aiden, how are you? How is the project coming along?
    110 |
  • 111 |
  • 112 |
    113 | 10:12 AM, Today 114 |
    115 |
    Are we meeting today?
    116 |
  • 117 |
  • 118 |
    119 | 10:15 AM, Today 120 |
    121 |
    Project has been already finished and I have results to show you.
    122 |
  • 123 |
124 |
125 |
126 |
127 |
128 | 129 |
130 | 131 |
132 |
133 |
134 | } 135 | 136 |
137 |
138 |
139 |
140 | ``` 141 | 142 | - **TS** 143 | ```ts 144 | import { CommonModule } from '@angular/common'; 145 | import { Component } from '@angular/core'; 146 | import { RouterOutlet } from '@angular/router'; 147 | 148 | @Component({ 149 | selector: 'app-root', 150 | standalone: true, 151 | imports: [RouterOutlet, CommonModule], 152 | templateUrl: './app.component.html', 153 | styleUrl: './app.component.css' 154 | }) 155 | export class AppComponent { 156 | users = Users; 157 | chats = Chats; 158 | selectedUserId: string = "1"; 159 | selectedUser: UserModel = { 160 | id: "1", 161 | name: "Vincent Porter", 162 | status: "left 7 min ago", 163 | avatar: "avatar1.png" 164 | }; 165 | 166 | 167 | changeUser(user: UserModel){ 168 | this.selectedUserId = user.id; 169 | this.selectedUser = user; 170 | 171 | this.chats = Chats.filter(p=> p.toUserId == user.id && p.userId == "0" || p.userId == user.id && p.toUserId == "0"); 172 | } 173 | 174 | } 175 | 176 | export class UserModel{ 177 | id:string = ""; 178 | name: string = ""; 179 | status: string = ""; 180 | avatar: string = ""; 181 | } 182 | 183 | export const Users: UserModel[] = [ 184 | { 185 | id: "1", 186 | name: "Vincent Porter", 187 | status: "left 7 min ago", 188 | avatar: "avatar1.png" 189 | }, 190 | { 191 | id: "2", 192 | name: "Aiden Chavez", 193 | status: "online", 194 | avatar: "avatar3.png" 195 | }, 196 | { 197 | id: "3", 198 | name: "Christian Kelly", 199 | status: "offline since oct 28", 200 | avatar: "avatar3.png" 201 | } 202 | ] 203 | 204 | export class ChatModel{ 205 | userId: string = ""; 206 | toUserId: string = ""; 207 | date: string =""; 208 | message: string = ""; 209 | } 210 | 211 | export const Chats: ChatModel[] = [ 212 | { 213 | userId: "0", 214 | toUserId: "1", 215 | date: new Date().toString(), 216 | message: "Hi Aiden, how are you? How is the project coming along?" 217 | }, 218 | { 219 | userId: "1", 220 | toUserId: "0", 221 | date: new Date().toString(), 222 | message: "Are we meeting today?" 223 | }, 224 | { 225 | userId: "1", 226 | toUserId: "0", 227 | date: new Date().toString(), 228 | message: "Project has been already finished and I have results to show you." 229 | } 230 | ] 231 | ``` 232 | 233 | - **CSS** 234 | ```css 235 | body{ 236 | background-color: #f4f7f6; 237 | margin-top:20px; 238 | } 239 | .card { 240 | background: #fff; 241 | transition: .5s; 242 | border: 0; 243 | margin-bottom: 30px; 244 | border-radius: .55rem; 245 | position: relative; 246 | width: 100%; 247 | box-shadow: 0 1px 2px 0 rgb(0 0 0 / 10%); 248 | } 249 | .chat-app .people-list { 250 | width: 280px; 251 | position: absolute; 252 | left: 0; 253 | top: 0; 254 | padding: 20px; 255 | z-index: 7 256 | } 257 | 258 | .chat-app .chat { 259 | margin-left: 280px; 260 | border-left: 1px solid #eaeaea 261 | } 262 | 263 | .people-list { 264 | -moz-transition: .5s; 265 | -o-transition: .5s; 266 | -webkit-transition: .5s; 267 | transition: .5s 268 | } 269 | 270 | .people-list .chat-list li { 271 | padding: 10px 15px; 272 | list-style: none; 273 | border-radius: 3px 274 | } 275 | 276 | .people-list .chat-list li:hover { 277 | background: #efefef; 278 | cursor: pointer 279 | } 280 | 281 | .people-list .chat-list li.active { 282 | background: #efefef 283 | } 284 | 285 | .people-list .chat-list li .name { 286 | font-size: 15px 287 | } 288 | 289 | .people-list .chat-list img { 290 | width: 45px; 291 | border-radius: 50% 292 | } 293 | 294 | .people-list img { 295 | float: left; 296 | border-radius: 50% 297 | } 298 | 299 | .people-list .about { 300 | float: left; 301 | padding-left: 8px 302 | } 303 | 304 | .people-list .status { 305 | color: #999; 306 | font-size: 13px 307 | } 308 | 309 | .chat .chat-header { 310 | padding: 15px 20px; 311 | border-bottom: 2px solid #f4f7f6 312 | } 313 | 314 | .chat .chat-header img { 315 | float: left; 316 | border-radius: 40px; 317 | width: 40px 318 | } 319 | 320 | .chat .chat-header .chat-about { 321 | float: left; 322 | padding-left: 10px 323 | } 324 | 325 | .chat .chat-history { 326 | padding: 20px; 327 | border-bottom: 2px solid #fff 328 | } 329 | 330 | .chat .chat-history ul { 331 | padding: 0 332 | } 333 | 334 | .chat .chat-history ul li { 335 | list-style: none; 336 | margin-bottom: 30px 337 | } 338 | 339 | .chat .chat-history ul li:last-child { 340 | margin-bottom: 0px 341 | } 342 | 343 | .chat .chat-history .message-data { 344 | margin-bottom: 15px 345 | } 346 | 347 | .chat .chat-history .message-data img { 348 | border-radius: 40px; 349 | width: 40px 350 | } 351 | 352 | .chat .chat-history .message-data-time { 353 | color: #434651; 354 | padding-left: 6px 355 | } 356 | 357 | .chat .chat-history .message { 358 | color: #444; 359 | padding: 18px 20px; 360 | line-height: 26px; 361 | font-size: 16px; 362 | border-radius: 7px; 363 | display: inline-block; 364 | position: relative 365 | } 366 | 367 | .chat .chat-history .message:after { 368 | bottom: 100%; 369 | left: 7%; 370 | border: solid transparent; 371 | content: " "; 372 | height: 0; 373 | width: 0; 374 | position: absolute; 375 | pointer-events: none; 376 | border-bottom-color: #fff; 377 | border-width: 10px; 378 | margin-left: -10px 379 | } 380 | 381 | .chat .chat-history .my-message { 382 | background: #efefef 383 | } 384 | 385 | .chat .chat-history .my-message:after { 386 | bottom: 100%; 387 | left: 30px; 388 | border: solid transparent; 389 | content: " "; 390 | height: 0; 391 | width: 0; 392 | position: absolute; 393 | pointer-events: none; 394 | border-bottom-color: #efefef; 395 | border-width: 10px; 396 | margin-left: -10px 397 | } 398 | 399 | .chat .chat-history .other-message { 400 | background: #e8f1f3; 401 | text-align: right 402 | } 403 | 404 | .chat .chat-history .other-message:after { 405 | border-bottom-color: #e8f1f3; 406 | left: 93% 407 | } 408 | 409 | .chat .chat-message { 410 | padding: 20px 411 | } 412 | 413 | .online, 414 | .offline, 415 | .me { 416 | margin-right: 2px; 417 | font-size: 8px; 418 | vertical-align: middle 419 | } 420 | 421 | .online { 422 | color: #86c541 423 | } 424 | 425 | .offline { 426 | color: #e47297 427 | } 428 | 429 | .me { 430 | color: #1d8ecd 431 | } 432 | 433 | .float-right { 434 | float: right 435 | } 436 | 437 | .clearfix:after { 438 | visibility: hidden; 439 | display: block; 440 | font-size: 0; 441 | content: " "; 442 | clear: both; 443 | height: 0 444 | } 445 | 446 | @media only screen and (max-width: 767px) { 447 | .chat-app .people-list { 448 | height: 465px; 449 | width: 100%; 450 | overflow-x: auto; 451 | background: #fff; 452 | left: -400px; 453 | display: none 454 | } 455 | .chat-app .people-list.open { 456 | left: 0 457 | } 458 | .chat-app .chat { 459 | margin: 0 460 | } 461 | .chat-app .chat .chat-header { 462 | border-radius: 0.55rem 0.55rem 0 0 463 | } 464 | .chat-app .chat-history { 465 | height: 300px; 466 | overflow-x: auto 467 | } 468 | } 469 | 470 | @media only screen and (min-width: 768px) and (max-width: 992px) { 471 | .chat-app .chat-list { 472 | height: 650px; 473 | overflow-x: auto 474 | } 475 | .chat-app .chat-history { 476 | height: 600px; 477 | overflow-x: auto 478 | } 479 | } 480 | 481 | @media only screen and (min-device-width: 768px) and (max-device-width: 1024px) and (orientation: landscape) and (-webkit-min-device-pixel-ratio: 1) { 482 | .chat-app .chat-list { 483 | height: 480px; 484 | overflow-x: auto 485 | } 486 | .chat-app .chat-history { 487 | height: calc(100vh - 350px); 488 | overflow-x: auto 489 | } 490 | } 491 | ``` 492 | 493 | - **index.HTML** 494 | ```html 495 | 496 | 497 | 498 | 499 | ChatAppClient 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | ``` --------------------------------------------------------------------------------