diff --git a/package-lock.json b/package-lock.json index ab0e168..deeffd1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { - "name": "orgexplorer", + "name": "OrgExplorer", "version": "0.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "orgexplorer", + "name": "OrgExplorer", "version": "0.0.0", "dependencies": { "react": "^19.2.0", @@ -57,6 +57,7 @@ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -959,6 +960,7 @@ "integrity": "sha512-ne4A0IpG3+2ETuREInjPNhUGis1SFjv1d5asp8MzEAGtOZeTeHVDOYqOgqfhvseqg/iXty2hjBf1zAOb7RNiNw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~7.16.0" } @@ -969,6 +971,7 @@ "integrity": "sha512-WPigyYuGhgZ/cTPRXB2EwUw+XvsRA3GqHlsP4qteqrnnjDrApbS7MxcGr/hke5iUoeB7E/gQtrs9I37zAJ0Vjw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -1028,6 +1031,7 @@ "integrity": "sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.54.0", "@typescript-eslint/types": "8.54.0", @@ -1279,6 +1283,7 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -1384,6 +1389,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -1573,6 +1579,7 @@ "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -2520,6 +2527,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -2581,6 +2589,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -2590,6 +2599,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -2789,6 +2799,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -2876,6 +2887,7 @@ "integrity": "sha512-u09tdk/huMiN8xwoiBbig197jKdCamQTtOruSalOzbqGje3jdHiV0njQlAW0YvzoahkirFePNQ4RYlfnRQpXZA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@oxc-project/runtime": "0.97.0", "fdir": "^6.5.0", @@ -2998,6 +3010,7 @@ "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", "dev": true, "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/src/App.css b/src/App.css index 027945e..0a36f0d 100644 --- a/src/App.css +++ b/src/App.css @@ -1,6 +1,84 @@ #root { - max-width: 1280px; - margin: 0 auto; - padding: 2rem; + width: 100%; +} + +.app-container { + min-height: 100vh; + min-height: 100dvh; + + display: flex; + flex-direction: column; + align-items: center; + + padding: 40px; + + background-color: #111; + color: white; +} + +.app-container h1 { + margin-bottom: 30px; + font-size: 3rem; +} + +form { + display: flex; + gap: 12px; + margin-bottom: 30px; +} + +input { + padding: 12px; + width: 300px; + + border: none; + border-radius: 10px; + + font-size: 16px; +} + +button { + padding: 12px 20px; + + border: none; + border-radius: 10px; + + cursor: pointer; + + font-size: 16px; + font-weight: bold; +} + +.org-card { + max-width: 500px; + + padding: 30px; + + border-radius: 16px; + + background-color: #1d1d1d; + text-align: center; +} + +.org-card img { + border-radius: 50%; + margin-bottom: 20px; +} + +.org-card h2 { + margin-bottom: 10px; +} + +.org-card p { + margin-bottom: 12px; +} + +.org-card a { + color: #4ea1ff; + text-decoration: none; +} + +.org-card a:hover { + text-decoration: underline; } \ No newline at end of file diff --git a/src/App.tsx b/src/App.tsx index 0a3deb1..c1a04e0 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,12 +1,58 @@ +import { useState } from 'react' + import './App.css' +import OrgCard from './components/orgcard' +import SearchBar from "./components/searchbar"; + +import { fetchOrganization } from './services/githubapi' + +import type { GitHubOrg } from './types/github' + function App() { + const [organization, setOrganization] = + useState(null) + + const [loading, setLoading] = useState(false) + + const [error, setError] = useState('') + + async function handleSearch(orgName: string) { + try { + setLoading(true) + setError('') + + const data = await fetchOrganization(orgName) + + setOrganization(data) + } catch (error) { + setOrganization(null) + + if (error instanceof Error) { + setError(error.message) + } else { + setError('Something went wrong') + } + } finally { + setLoading(false) + } + } return ( - <> -

Hello, OrgExplorer!

- +
+

OrgExplorer

+ + + + {loading &&

Loading...

} + + {error &&

{error}

} + + {organization && ( + + )} +
) } -export default App +export default App \ No newline at end of file diff --git a/src/components/orgcard.tsx b/src/components/orgcard.tsx new file mode 100644 index 0000000..18cbdd4 --- /dev/null +++ b/src/components/orgcard.tsx @@ -0,0 +1,38 @@ +import type { GitHubOrg } from '../types/github' + +interface OrgCardProps { + organization: GitHubOrg +} + +function OrgCard({ organization }: OrgCardProps) { + return ( +
+ {organization.login} + +

{organization.login}

+ +

+ {organization.description ?? 'No description available'} +

+ +

Followers: {organization.followers}

+ +

Public Repositories: {organization.public_repos}

+ + + Visit GitHub Profile + +
+ ) +} + +export default OrgCard \ No newline at end of file diff --git a/src/components/searchbar.tsx b/src/components/searchbar.tsx new file mode 100644 index 0000000..7251d28 --- /dev/null +++ b/src/components/searchbar.tsx @@ -0,0 +1,35 @@ +import { useState } from 'react' + +interface SearchBarProps { + onSearch: (orgName: string) => void +} + +function SearchBar({ onSearch }: SearchBarProps) { + const [input, setInput] = useState('') + + function handleSubmit(event: React.FormEvent) { + event.preventDefault() + + if (!input.trim()) { + return + } + + onSearch(input) + setInput('') + } + + return ( +
+ setInput(event.target.value)} + /> + + +
+ ) +} + +export default SearchBar \ No newline at end of file diff --git a/src/index.css b/src/index.css index e0dbee4..82a5c55 100644 --- a/src/index.css +++ b/src/index.css @@ -1,15 +1,11 @@ -:root { - font-family: system-ui, Avenir, Helvetica, Arial, sans-serif; - line-height: 1.5; - font-weight: 400; - - color-scheme: light dark; - color: rgba(255, 255, 255, 0.87); - background-color: #242424; - - font-synthesis: none; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; +* { + margin: 0; + padding: 0; + box-sizing: border-box; } +body { + font-family: Arial, sans-serif; + background-color: #111; + color: white; +} \ No newline at end of file diff --git a/src/main.tsx b/src/main.tsx index bef5202..557a22d 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,5 +1,6 @@ import { StrictMode } from 'react' import { createRoot } from 'react-dom/client' + import './index.css' import App from './App.tsx' @@ -7,4 +8,4 @@ createRoot(document.getElementById('root')!).render( , -) +) \ No newline at end of file diff --git a/src/services/githubapi.ts b/src/services/githubapi.ts new file mode 100644 index 0000000..7cfb452 --- /dev/null +++ b/src/services/githubapi.ts @@ -0,0 +1,23 @@ +import type { GitHubOrg } from '../types/github' + +export async function fetchOrganization( + orgName: string, +): Promise { + const response = await fetch( + `https://api.github.com/orgs/${encodeURIComponent(orgName)}`, + ) + + if (response.status === 404) { + throw new Error('Organization not found') + } + + if (response.status === 403) { + throw new Error('GitHub API access denied or rate limit exceeded') + } + + if (!response.ok) { + throw new Error('Something went wrong') + } + + return response.json() +} \ No newline at end of file diff --git a/src/types/github.ts b/src/types/github.ts new file mode 100644 index 0000000..6787c5d --- /dev/null +++ b/src/types/github.ts @@ -0,0 +1,8 @@ +export interface GitHubOrg { + login: string + avatar_url: string + description: string | null + public_repos: number + followers: number + html_url: string +} \ No newline at end of file