Initial commit

This commit is contained in:
2026-01-14 10:43:02 +06:00
commit 3c541a2ba5
17 changed files with 526 additions and 0 deletions

2
frontend/.env Normal file
View File

@@ -0,0 +1,2 @@
REACT_APP_API_URL=http://localhost:8000
REACT_APP_WS_URL=ws://localhost:8000

39
frontend/package.json Normal file
View File

@@ -0,0 +1,39 @@
{
"name": "video-streaming-frontend",
"version": "0.1.0",
"private": true,
"dependencies": {
"@types/node": "^20.10.0",
"@types/react": "^18.2.45",
"@types/react-dom": "^18.2.18",
"axios": "^1.6.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.21.0",
"react-scripts": "5.0.1",
"typescript": "^5.3.3"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}

View File

@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta name="description" content="Платформа для стриминга видео" />
<title>Video Streaming</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
</html>

31
frontend/src/api.ts Normal file
View File

@@ -0,0 +1,31 @@
import axios from 'axios';
const API_URL = process.env.REACT_APP_API_URL || 'http://localhost:8000';
const api = axios.create({
baseURL: API_URL,
});
api.interceptors.request.use((config) => {
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
export const authAPI = {
getLoginUrl: () => api.get('/auth/login'),
callback: (code: string) => api.post('/auth/callback', { code }),
getMe: () => api.get('/me'),
};
export const streamAPI = {
getStreams: () => api.get('/streams'),
getStream: (id: string) => api.get(`/streams/${id}`),
createStream: (data: { title: string; description?: string }) =>
api.post('/streams', data),
endStream: (id: string) => api.delete(`/streams/${id}`),
};
export default api;

View File

View File

@@ -0,0 +1,52 @@
import React, { createContext, useContext, useState, useEffect } from 'react';
import { User, AuthContextType } from '../types';
import { authAPI } from '../api';
const AuthContext = createContext<AuthContextType | undefined>(undefined);
export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [user, setUser] = useState<User | null>(null);
const [token, setToken] = useState<string | null>(localStorage.getItem('token'));
useEffect(() => {
if (token) {
authAPI.getMe()
.then(response => setUser(response.data))
.catch(() => {
localStorage.removeItem('token');
setToken(null);
});
}
}, [token]);
const login = async () => {
const response = await authAPI.getLoginUrl();
window.location.href = response.data.auth_url;
};
const logout = () => {
localStorage.removeItem('token');
setToken(null);
setUser(null);
};
return (
<AuthContext.Provider value={{
user,
token,
login,
logout,
isAuthenticated: !!token
}}>
{children}
</AuthContext.Provider>
);
};
export const useAuth = () => {
const context = useContext(AuthContext);
if (!context) {
throw new Error('useAuth must be used within AuthProvider');
}
return context;
};

25
frontend/src/types.ts Normal file
View File

@@ -0,0 +1,25 @@
export interface User {
id: string;
email: string;
name: string;
picture?: string;
}
export interface Stream {
id: string;
user_id: string;
title: string;
description?: string;
is_live: boolean;
viewer_count: number;
created_at: string;
thumbnail?: string;
}
export interface AuthContextType {
user: User | null;
token: string | null;
login: () => void;
logout: () => void;
isAuthenticated: boolean;
}

20
frontend/tsconfig.json Normal file
View File

@@ -0,0 +1,20 @@
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": ["src"]
}