Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
aliirz committed Nov 6, 2024
0 parents commit 009904c
Show file tree
Hide file tree
Showing 44 changed files with 7,708 additions and 0 deletions.
10 changes: 10 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# editorconfig.org
root = true

[*]
charset = utf-8
end_of_line = lf
indent_size = 2
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
5 changes: 5 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
dist/*
.cache
public
node_modules
*.esm.js
30 changes: 30 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"$schema": "https://json.schemastore.org/eslintrc",
"root": true,
"extends": [
"next/core-web-vitals",
"prettier",
"plugin:tailwindcss/recommended"
],
"plugins": ["tailwindcss"],
"rules": {
"@next/next/no-html-link-for-pages": "off",
"react/jsx-key": "off",
"tailwindcss/no-custom-classname": "off"
},
"settings": {
"tailwindcss": {
"callees": ["cn"],
"config": "tailwind.config.js"
},
"next": {
"rootDir": ["./"]
}
},
"overrides": [
{
"files": ["*.ts", "*.tsx"],
"parser": "@typescript-eslint/parser"
}
]
}
36 changes: 36 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
node_modules
.pnp
.pnp.js

# testing
coverage

# next.js
.next/
out/
build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*

# local env files
.env.local
.env.development.local
.env.test.local
.env.production.local

# turbo
.turbo

.contentlayer
.env
12 changes: 12 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
cache
.cache
package.json
package-lock.json
public
CHANGELOG.md
.yarn
dist
node_modules
.next
build
.contentlayer
4 changes: 4 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"typescript.tsdk": "../../node_modules/.pnpm/[email protected]/node_modules/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true
}
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# next-template

A Next.js 13 template for building apps with Radix UI and Tailwind CSS.

## Usage

```bash
npx create-next-app -e https://github.com/shadcn/next-template
```

## Features

- Next.js 13 App Directory
- Radix UI Primitives
- Tailwind CSS
- Icons from [Lucide](https://lucide.dev)
- Dark mode with `next-themes`
- Tailwind CSS class sorting, merging and linting.

## License

Licensed under the [MIT license](https://github.com/shadcn/ui/blob/main/LICENSE.md).
146 changes: 146 additions & 0 deletions app/auth/callback/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
'use client'

import { useEffect, useState } from 'react'
import { useRouter } from 'next/navigation'
import { useToast } from '@/hooks/use-toast'

export default function AuthCallback() {
const router = useRouter()
const { toast } = useToast()
const [isProcessing, setIsProcessing] = useState(true)

useEffect(() => {
const handleCallback = async () => {
try {
const params = new URLSearchParams(window.location.search)

// Check for direct token response first (implicit flow)
const accessToken = params.get('access_token')
const idToken = params.get('id_token')

if (accessToken) {
// We received tokens directly
localStorage.setItem('access_token', accessToken)
if (idToken) localStorage.setItem('id_token', idToken)

// Fetch user info
const userResponse = await fetch(`${process.env.NEXT_PUBLIC_PEHCHAN_URL}/api/auth/userinfo`, {
headers: {
'Authorization': `Bearer ${accessToken}`
}
})

if (!userResponse.ok) {
throw new Error('Failed to fetch user info')
}

const userInfo = await userResponse.json()
localStorage.setItem('user_info', JSON.stringify(userInfo))

toast({
title: "Login successful",
description: "Welcome to FBR Tax Portal"
})

router.push('/dashboard')
return
}

// If no direct tokens, check for authorization code flow
const code = params.get('code')
const state = params.get('state')
const error = params.get('error')
const storedState = sessionStorage.getItem('auth_state')

// Check for errors
if (error) {
throw new Error(error)
}

// For code flow
if (code) {
// Validate state if present
if (state && state !== storedState) {
throw new Error('Invalid state parameter')
}

// Exchange code for tokens
const tokenResponse = await fetch(`${process.env.NEXT_PUBLIC_PEHCHAN_URL}/api/token`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
code,
client_id: process.env.NEXT_PUBLIC_CLIENT_ID,
redirect_uri: `${window.location.origin}/auth/callback`,
grant_type: 'authorization_code',
}),
})

if (!tokenResponse.ok) {
throw new Error('Token exchange failed')
}

const tokens = await tokenResponse.json()

// Store tokens and proceed as before
localStorage.setItem('access_token', tokens.access_token)
localStorage.setItem('id_token', tokens.id_token)

// Fetch user info
const userResponse = await fetch(`${process.env.NEXT_PUBLIC_PEHCHAN_URL}/api/auth/userinfo`, {
headers: {
'Authorization': `Bearer ${tokens.access_token}`
}
})

if (!userResponse.ok) {
throw new Error('Failed to fetch user info')
}

const userInfo = await userResponse.json()
localStorage.setItem('user_info', JSON.stringify(userInfo))

toast({
title: "Login successful",
description: "Welcome to FBR Tax Portal"
})

router.push('/dashboard')
return
}

// If we get here, no valid authentication data was received
throw new Error('No valid authentication data received')

} catch (error) {
console.error('Auth callback error:', error)
toast({
variant: "destructive",
title: "Authentication Failed",
description: error instanceof Error ? error.message : "An error occurred during login"
})
router.push('/')
} finally {
sessionStorage.removeItem('auth_state')
setIsProcessing(false)
}
}

handleCallback()
}, [router, toast])

if (isProcessing) {
return (
<div className="min-h-screen flex items-center justify-center">
<div className="text-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-gray-900 mx-auto mb-4"></div>
<p className="text-gray-600">Processing your login...</p>
</div>
</div>
)
}

return null
}
103 changes: 103 additions & 0 deletions app/dashboard/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
'use client'

import { useEffect, useState } from 'react'
import { useRouter } from 'next/navigation'
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { useToast } from '@/hooks/use-toast'

interface UserInfo {
sub: string
email: string
name: string
profile?: {
cnic: string
phone: string
}
}

export default function Dashboard() {
const [userInfo, setUserInfo] = useState<UserInfo | null>(null)
const router = useRouter()
const { toast } = useToast()

useEffect(() => {
// Check authentication
const accessToken = localStorage.getItem('access_token')
const storedUserInfo = localStorage.getItem('user_info')

if (!accessToken || !storedUserInfo) {
router.push('/')
return
}

setUserInfo(JSON.parse(storedUserInfo))
}, [router])

const handleLogout = () => {
localStorage.clear()
toast({
title: "Logged out",
description: "You have been successfully logged out"
})
router.push('/')
}

if (!userInfo) {
return null
}

return (
<div className="max-w-4xl mx-auto p-6">
<Card className="mb-6">
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-2xl font-bold">FBR Tax Portal</CardTitle>
<Button
variant="destructive"
onClick={handleLogout}
>
Logout
</Button>
</CardHeader>
</Card>

<div className="grid md:grid-cols-2 gap-6">
<Card>
<CardHeader>
<CardTitle>User Information</CardTitle>
</CardHeader>
<CardContent className="space-y-2">
<div className="grid grid-cols-3 gap-4">
<span className="font-semibold">Name:</span>
<span className="col-span-2">{userInfo.name}</span>
</div>
<div className="grid grid-cols-3 gap-4">
<span className="font-semibold">Email:</span>
<span className="col-span-2">{userInfo.email}</span>
</div>
<div className="grid grid-cols-3 gap-4">
<span className="font-semibold">CNIC:</span>
<span className="col-span-2">{userInfo.profile?.cnic}</span>
</div>
<div className="grid grid-cols-3 gap-4">
<span className="font-semibold">Phone:</span>
<span className="col-span-2">{userInfo.profile?.phone}</span>
</div>
</CardContent>
</Card>

<Card>
<CardHeader>
<CardTitle>Tax Filing Status</CardTitle>
</CardHeader>
<CardContent>
<p className="text-sm text-gray-600 mb-4">
You can now proceed with your tax filing. Your information has been pre-filled from your Pehchan ID.
</p>
<Button className="w-full">Start Tax Filing</Button>
</CardContent>
</Card>
</div>
</div>
)
}
Loading

0 comments on commit 009904c

Please sign in to comment.