Reformat
This commit is contained in:
parent
ea882652c0
commit
d99a45d922
54 changed files with 1199 additions and 1329 deletions
|
|
@ -5,18 +5,20 @@
|
|||
<option name="HTML_ENFORCE_QUOTES" value="true" />
|
||||
</HTMLCodeStyleSettings>
|
||||
<JSCodeStyleSettings version="0">
|
||||
<option name="USE_SEMICOLON_AFTER_STATEMENT" value="false" />
|
||||
<option name="FORCE_SEMICOLON_STYLE" value="true" />
|
||||
<option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
|
||||
<option name="FORCE_QUOTE_STYlE" value="true" />
|
||||
<option name="ENFORCE_TRAILING_COMMA" value="Remove" />
|
||||
<option name="ENFORCE_TRAILING_COMMA" value="WhenMultiline" />
|
||||
<option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
|
||||
<option name="SPACES_WITHIN_IMPORTS" value="true" />
|
||||
</JSCodeStyleSettings>
|
||||
<TypeScriptCodeStyleSettings version="0">
|
||||
<option name="USE_SEMICOLON_AFTER_STATEMENT" value="false" />
|
||||
<option name="FORCE_SEMICOLON_STYLE" value="true" />
|
||||
<option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
|
||||
<option name="FORCE_QUOTE_STYlE" value="true" />
|
||||
<option name="ENFORCE_TRAILING_COMMA" value="Remove" />
|
||||
<option name="ENFORCE_TRAILING_COMMA" value="WhenMultiline" />
|
||||
<option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
|
||||
<option name="SPACES_WITHIN_IMPORTS" value="true" />
|
||||
</TypeScriptCodeStyleSettings>
|
||||
|
|
|
|||
|
|
@ -1 +1,4 @@
|
|||
{}
|
||||
printWidth: 100
|
||||
semi: false
|
||||
trailingComma: all
|
||||
bracketSameLine: false
|
||||
|
|
|
|||
|
|
@ -1,21 +1,23 @@
|
|||
import fetch, {RequestInit, Response} from "node-fetch";
|
||||
import fetch, { RequestInit, Response } from "node-fetch"
|
||||
|
||||
export async function userNickname(userId: string): Promise<string | null> {
|
||||
const url = new URL(`${process.env.AUTH0_ISSUER_BASE_URL}/api/v2/users/${userId}`);
|
||||
const params = new URLSearchParams({
|
||||
fields: 'nickname',
|
||||
include_fields: 'true'
|
||||
});
|
||||
url.search = params.toString();
|
||||
const url = new URL(`${process.env.AUTH0_ISSUER_BASE_URL}/api/v2/users/${userId}`)
|
||||
const params = new URLSearchParams({
|
||||
fields: "nickname",
|
||||
include_fields: "true",
|
||||
})
|
||||
url.search = params.toString()
|
||||
|
||||
const options: RequestInit = {
|
||||
method: 'GET',
|
||||
headers: {authorization: `Bearer ${process.env.AUTH0_MANAGEMENT_API_TOKEN}`}
|
||||
};
|
||||
const options: RequestInit = {
|
||||
method: "GET",
|
||||
headers: {
|
||||
authorization: `Bearer ${process.env.AUTH0_MANAGEMENT_API_TOKEN}`,
|
||||
},
|
||||
}
|
||||
|
||||
const response: Response = await fetch(url, options);
|
||||
if (response.ok) {
|
||||
const user: { 'nickname' } = await response.json();
|
||||
return user.nickname;
|
||||
} else return null;
|
||||
const response: Response = await fetch(url, options)
|
||||
if (response.ok) {
|
||||
const user: { nickname } = await response.json()
|
||||
return user.nickname
|
||||
} else return null
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,115 +1,116 @@
|
|||
import {FileDiffContent} from "code-suggester/build/src/types"
|
||||
import { FileDiffContent } from "code-suggester/build/src/types"
|
||||
import Octokit from "@octokit/rest"
|
||||
|
||||
export async function createPullRequestFromDiffContents(
|
||||
installationOctokit: Octokit,
|
||||
owner: string,
|
||||
repo: string,
|
||||
pullNumber: number,
|
||||
diffContentsMap: Map<string, FileDiffContent>
|
||||
installationOctokit: Octokit,
|
||||
owner: string,
|
||||
repo: string,
|
||||
pullNumber: number,
|
||||
diffContentsMap: Map<string, FileDiffContent>,
|
||||
): Promise<Octokit.Response<Octokit.PullsCreateResponse>> {
|
||||
|
||||
// Create a new branch
|
||||
const newBranchName = `codeflash-suggestions-for-pr${pullNumber}-${Date.now()}`;
|
||||
await installationOctokit.rest.git.createRef({
|
||||
// Create a new branch
|
||||
const newBranchName = `codeflash-suggestions-for-pr${pullNumber}-${Date.now()}`
|
||||
await installationOctokit.rest.git.createRef({
|
||||
owner,
|
||||
repo,
|
||||
ref: `refs/heads/${newBranchName}`,
|
||||
sha: (
|
||||
await installationOctokit.rest.git.getRef({
|
||||
owner,
|
||||
repo,
|
||||
ref: `refs/heads/${newBranchName}`,
|
||||
sha: (await installationOctokit.rest.git.getRef({
|
||||
owner,
|
||||
repo,
|
||||
ref: `heads/main`,
|
||||
})).data.object.sha,
|
||||
});
|
||||
ref: `heads/main`,
|
||||
})
|
||||
).data.object.sha,
|
||||
})
|
||||
|
||||
// Apply changes from diffContentsMap to the new branch and commit
|
||||
const treeItems: Octokit.GitCreateTreeParamsTree[] = [];
|
||||
for (const [filePath, fileDiffContent] of diffContentsMap) {
|
||||
// Create blobs for the changed files with newContent
|
||||
const blobData = await installationOctokit.rest.git.createBlob({
|
||||
owner,
|
||||
repo,
|
||||
content: fileDiffContent.newContent,
|
||||
encoding: 'utf-8',
|
||||
});
|
||||
|
||||
treeItems.push({
|
||||
path: filePath,
|
||||
mode: '100644', // blob
|
||||
type: 'blob',
|
||||
sha: blobData.data.sha,
|
||||
});
|
||||
}
|
||||
|
||||
// Get the current commit SHA of the new branch
|
||||
const refData = await installationOctokit.rest.git.getRef({
|
||||
owner,
|
||||
repo,
|
||||
ref: `heads/${newBranchName}`,
|
||||
});
|
||||
const currentCommitSha = refData.data.object.sha;
|
||||
|
||||
// Get the tree SHA of the current commit
|
||||
const commitData = await installationOctokit.rest.git.getCommit({
|
||||
owner,
|
||||
repo,
|
||||
commit_sha: currentCommitSha,
|
||||
});
|
||||
const currentTreeSha = commitData.data.tree.sha;
|
||||
|
||||
// Create a new tree with the changes
|
||||
const newTreeData = await installationOctokit.rest.git.createTree({
|
||||
owner,
|
||||
repo,
|
||||
base_tree: currentTreeSha,
|
||||
tree: treeItems,
|
||||
});
|
||||
|
||||
// Create a new commit with the new tree
|
||||
const newCommitData = await installationOctokit.rest.git.createCommit({
|
||||
owner,
|
||||
repo,
|
||||
message: `Code Suggestions for PR #${pullNumber}`,
|
||||
tree: newTreeData.data.sha,
|
||||
parents: [currentCommitSha],
|
||||
});
|
||||
|
||||
// Update the new branch reference to point to the new commit
|
||||
await installationOctokit.rest.git.updateRef({
|
||||
owner,
|
||||
repo,
|
||||
ref: `heads/${newBranchName}`,
|
||||
sha: newCommitData.data.sha,
|
||||
});
|
||||
|
||||
// Get the head branch of the original pull request
|
||||
const originalPrData = await installationOctokit.rest.pulls.get({
|
||||
owner,
|
||||
repo,
|
||||
pull_number: pullNumber,
|
||||
});
|
||||
const targetBranch = originalPrData.data.head.ref;
|
||||
|
||||
// Open a new PR from the new branch onto the original PR's head branch
|
||||
const newPrData = await installationOctokit.rest.pulls.create({
|
||||
owner,
|
||||
repo,
|
||||
title: `CodeFlash Suggestions for PR #${pullNumber} ⚡️`,
|
||||
head: newBranchName,
|
||||
base: targetBranch,
|
||||
body: `This pull request contains code suggestions for PR #${pullNumber}.
|
||||
If you approve this dependent PR, these changes will be merged into that PR (branch \`${targetBranch}\`).`
|
||||
});
|
||||
|
||||
// Make a comment on the original PR with a link to the new PR
|
||||
const result = await installationOctokit.rest.issues.createComment({
|
||||
owner: owner,
|
||||
repo: repo,
|
||||
issue_number: pullNumber,
|
||||
body: `## ⚡️ CodeFlash found optimizations for this PR
|
||||
I created a new dependent PR with the suggested changes: #${newPrData.data.number}
|
||||
If you approve, it will be merged into this PR (branch \`${targetBranch}\`).`,
|
||||
// Apply changes from diffContentsMap to the new branch and commit
|
||||
const treeItems: Octokit.GitCreateTreeParamsTree[] = []
|
||||
for (const [filePath, fileDiffContent] of diffContentsMap) {
|
||||
// Create blobs for the changed files with newContent
|
||||
const blobData = await installationOctokit.rest.git.createBlob({
|
||||
owner,
|
||||
repo,
|
||||
content: fileDiffContent.newContent,
|
||||
encoding: "utf-8",
|
||||
})
|
||||
|
||||
return newPrData
|
||||
treeItems.push({
|
||||
path: filePath,
|
||||
mode: "100644", // blob
|
||||
type: "blob",
|
||||
sha: blobData.data.sha,
|
||||
})
|
||||
}
|
||||
|
||||
// Get the current commit SHA of the new branch
|
||||
const refData = await installationOctokit.rest.git.getRef({
|
||||
owner,
|
||||
repo,
|
||||
ref: `heads/${newBranchName}`,
|
||||
})
|
||||
const currentCommitSha = refData.data.object.sha
|
||||
|
||||
// Get the tree SHA of the current commit
|
||||
const commitData = await installationOctokit.rest.git.getCommit({
|
||||
owner,
|
||||
repo,
|
||||
commit_sha: currentCommitSha,
|
||||
})
|
||||
const currentTreeSha = commitData.data.tree.sha
|
||||
|
||||
// Create a new tree with the changes
|
||||
const newTreeData = await installationOctokit.rest.git.createTree({
|
||||
owner,
|
||||
repo,
|
||||
base_tree: currentTreeSha,
|
||||
tree: treeItems,
|
||||
})
|
||||
|
||||
// Create a new commit with the new tree
|
||||
const newCommitData = await installationOctokit.rest.git.createCommit({
|
||||
owner,
|
||||
repo,
|
||||
message: `Code Suggestions for PR #${pullNumber}`,
|
||||
tree: newTreeData.data.sha,
|
||||
parents: [currentCommitSha],
|
||||
})
|
||||
|
||||
// Update the new branch reference to point to the new commit
|
||||
await installationOctokit.rest.git.updateRef({
|
||||
owner,
|
||||
repo,
|
||||
ref: `heads/${newBranchName}`,
|
||||
sha: newCommitData.data.sha,
|
||||
})
|
||||
|
||||
// Get the head branch of the original pull request
|
||||
const originalPrData = await installationOctokit.rest.pulls.get({
|
||||
owner,
|
||||
repo,
|
||||
pull_number: pullNumber,
|
||||
})
|
||||
const targetBranch = originalPrData.data.head.ref
|
||||
|
||||
// Open a new PR from the new branch onto the original PR's head branch
|
||||
const newPrData = await installationOctokit.rest.pulls.create({
|
||||
owner,
|
||||
repo,
|
||||
title: `CodeFlash Suggestions for PR #${pullNumber} ⚡️`,
|
||||
head: newBranchName,
|
||||
base: targetBranch,
|
||||
body: `This pull request contains code suggestions for PR #${pullNumber}.
|
||||
If you approve this dependent PR, these changes will be merged into that PR (branch \`${targetBranch}\`).`,
|
||||
})
|
||||
|
||||
// Make a comment on the original PR with a link to the new PR
|
||||
const result = await installationOctokit.rest.issues.createComment({
|
||||
owner: owner,
|
||||
repo: repo,
|
||||
issue_number: pullNumber,
|
||||
body: `## ⚡️ CodeFlash found optimizations for this PR
|
||||
I created a new dependent PR with the suggested changes: #${newPrData.data.number}
|
||||
If you approve, it will be merged into this PR (branch \`${targetBranch}\`).`,
|
||||
})
|
||||
|
||||
return newPrData
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import {App} from "octokit";
|
||||
import { App } from "octokit"
|
||||
// import {getGithubAppPrivateKey, getGithubAppWebhookSecret} from "../common/azure-keyvault";
|
||||
//TODO the above import doesn't work, we have to do this vvv
|
||||
import azureKeyvault from "../common/azure-keyvault";
|
||||
import azureKeyvault from "../common/azure-keyvault"
|
||||
|
||||
const {getGithubAppPrivateKey, getGithubAppWebhookSecret} = azureKeyvault;
|
||||
const { getGithubAppPrivateKey, getGithubAppWebhookSecret } = azureKeyvault
|
||||
|
||||
const APP_ID: string = process.env.APP_ID || ''; // Replace with your GitHub App ID
|
||||
const APP_ID: string = process.env.APP_ID || "" // Replace with your GitHub App ID
|
||||
|
||||
const PRIVATE_KEY: string = await getGithubAppPrivateKey()
|
||||
const WEBHOOK_SECRET: string = await getGithubAppWebhookSecret()
|
||||
|
|
@ -15,90 +15,94 @@ export const webhookApiPath: string = "/api/github/webhooks"
|
|||
// See https://github.com/octokit/webhooks.js/#createnodemiddleware for all options
|
||||
|
||||
export const githubApp = new App({
|
||||
appId: APP_ID,
|
||||
privateKey: PRIVATE_KEY,
|
||||
webhooks: {
|
||||
secret: WEBHOOK_SECRET
|
||||
},
|
||||
});
|
||||
appId: APP_ID,
|
||||
privateKey: PRIVATE_KEY,
|
||||
webhooks: {
|
||||
secret: WEBHOOK_SECRET,
|
||||
},
|
||||
})
|
||||
|
||||
// Optional: Get & log the authenticated app's name
|
||||
const {data} = await githubApp.octokit.request('/app')
|
||||
const { data } = await githubApp.octokit.request("/app")
|
||||
|
||||
// Read more about custom logging: https://github.com/octokit/core.js#logging
|
||||
githubApp.octokit.log.debug(`Authenticated as '${data.name}'`)
|
||||
|
||||
githubApp.webhooks.on('installation', async ({octokit, payload}) => {
|
||||
console.log(`Received a new installation event: ${JSON.stringify(payload)}`)
|
||||
// Create an installation access token
|
||||
const installationAccessToken = await octokit.apps.createInstallationAccessToken({
|
||||
installation_id: payload.installation.id,
|
||||
});
|
||||
console.log(`Installation access token: ${installationAccessToken.token}`);
|
||||
|
||||
githubApp.webhooks.on("installation", async ({ octokit, payload }) => {
|
||||
console.log(`Received a new installation event: ${JSON.stringify(payload)}`)
|
||||
// Create an installation access token
|
||||
const installationAccessToken = await octokit.apps.createInstallationAccessToken({
|
||||
installation_id: payload.installation.id,
|
||||
})
|
||||
console.log(`Installation access token: ${installationAccessToken.token}`)
|
||||
})
|
||||
|
||||
githubApp.webhooks.on('pull_request.opened', async ({octokit, payload}) => {
|
||||
console.log(`Received a pull request event: ${JSON.stringify(payload)}`)
|
||||
try {
|
||||
await octokit.rest.issues.createComment({
|
||||
owner: payload.repository.owner.login,
|
||||
repo: payload.repository.name,
|
||||
issue_number: payload.pull_request.number,
|
||||
body: "Thanks for opening this pull request!"
|
||||
})
|
||||
githubApp.webhooks.on("pull_request.opened", async ({ octokit, payload }) => {
|
||||
console.log(`Received a pull request event: ${JSON.stringify(payload)}`)
|
||||
try {
|
||||
await octokit.rest.issues.createComment({
|
||||
owner: payload.repository.owner.login,
|
||||
repo: payload.repository.name,
|
||||
issue_number: payload.pull_request.number,
|
||||
body: "Thanks for opening this pull request!",
|
||||
})
|
||||
|
||||
// await octokit.rest.pulls.createReviewComment({
|
||||
// owner: payload.repository.owner.login,
|
||||
// repo: payload.repository.name,
|
||||
// pull_number: payload.pull_request.number,
|
||||
// body: "Thanks for opening this pull request!",
|
||||
// commit_id: payload.pull_request.head.sha,
|
||||
// path:
|
||||
// })
|
||||
console.log(`Posted pull request comment!`)
|
||||
} catch (error: any) {
|
||||
if (error.response) {
|
||||
console.error(`Error! Status: ${error.response.status}. Message: ${error.response.data.message}`)
|
||||
} else {
|
||||
console.error(error)
|
||||
}
|
||||
// await octokit.rest.pulls.createReviewComment({
|
||||
// owner: payload.repository.owner.login,
|
||||
// repo: payload.repository.name,
|
||||
// pull_number: payload.pull_request.number,
|
||||
// body: "Thanks for opening this pull request!",
|
||||
// commit_id: payload.pull_request.head.sha,
|
||||
// path:
|
||||
// })
|
||||
console.log(`Posted pull request comment!`)
|
||||
} catch (error: any) {
|
||||
if (error.response) {
|
||||
console.error(
|
||||
`Error! Status: ${error.response.status}. Message: ${error.response.data.message}`,
|
||||
)
|
||||
} else {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
githubApp.webhooks.on('installation.created', async ({octokit, payload}) => {
|
||||
console.log(`Received a installation.created event: ${JSON.stringify(payload)}`);
|
||||
try {
|
||||
console.log(`Successfully processed this!!!`);
|
||||
|
||||
} catch (error: any) {
|
||||
if (error.response) {
|
||||
console.error(`Error! Status: ${error.response.status}. Message: ${error.response.data.message}`)
|
||||
} else {
|
||||
console.error(error)
|
||||
}
|
||||
githubApp.webhooks.on("installation.created", async ({ octokit, payload }) => {
|
||||
console.log(`Received a installation.created event: ${JSON.stringify(payload)}`)
|
||||
try {
|
||||
console.log(`Successfully processed this!!!`)
|
||||
} catch (error: any) {
|
||||
if (error.response) {
|
||||
console.error(
|
||||
`Error! Status: ${error.response.status}. Message: ${error.response.data.message}`,
|
||||
)
|
||||
} else {
|
||||
console.error(error)
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
|
||||
githubApp.webhooks.on('installation_repositories.added', async ({octokit, payload}) => {
|
||||
console.log(`Received a installation_repositories.added event: ${JSON.stringify(payload)}`);
|
||||
try {
|
||||
console.log(`Successfully processed this!!!`);
|
||||
} catch (error: any) {
|
||||
if (error.response) {
|
||||
console.error(`Error! Status: ${error.response.status}. Message: ${error.response.data.message}`)
|
||||
} else {
|
||||
console.error(error)
|
||||
}
|
||||
githubApp.webhooks.on("installation_repositories.added", async ({ octokit, payload }) => {
|
||||
console.log(`Received a installation_repositories.added event: ${JSON.stringify(payload)}`)
|
||||
try {
|
||||
console.log(`Successfully processed this!!!`)
|
||||
} catch (error: any) {
|
||||
if (error.response) {
|
||||
console.error(
|
||||
`Error! Status: ${error.response.status}. Message: ${error.response.data.message}`,
|
||||
)
|
||||
} else {
|
||||
console.error(error)
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
|
||||
// Optional: Handle errors
|
||||
githubApp.webhooks.onError((error) => {
|
||||
if (error.name === 'AggregateError') {
|
||||
// Log Secret verification errors
|
||||
console.log(`Error processing request: ${error.event}`)
|
||||
} else {
|
||||
console.log(error)
|
||||
}
|
||||
})
|
||||
if (error.name === "AggregateError") {
|
||||
// Log Secret verification errors
|
||||
console.log(`Error processing request: ${error.event}`)
|
||||
} else {
|
||||
console.log(error)
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,71 +1,67 @@
|
|||
import { createNodeMiddleware } from "@octokit/webhooks";
|
||||
import express from "express";
|
||||
import suggester from "code-suggester";
|
||||
import { FileDiffContent } from "code-suggester/build/src/types";
|
||||
import { createNodeMiddleware } from "@octokit/webhooks"
|
||||
import express from "express"
|
||||
import suggester from "code-suggester"
|
||||
import { FileDiffContent } from "code-suggester/build/src/types"
|
||||
|
||||
import { determineValidHunks, objectToMap } from "./utils";
|
||||
import { createPullRequestFromDiffContents } from "./create-pr-from-diffcontents";
|
||||
import { githubApp, webhookApiPath } from "./github-app";
|
||||
import functions from "../common/token-functions";
|
||||
import dotenv from "dotenv";
|
||||
import { userNickname } from "./auth0";
|
||||
import { determineValidHunks, objectToMap } from "./utils"
|
||||
import { createPullRequestFromDiffContents } from "./create-pr-from-diffcontents"
|
||||
import { githubApp, webhookApiPath } from "./github-app"
|
||||
import functions from "../common/token-functions"
|
||||
import dotenv from "dotenv"
|
||||
import { userNickname } from "./auth0"
|
||||
|
||||
const userForAPIKey = functions.userForAPIKey;
|
||||
const userForAPIKey = functions.userForAPIKey
|
||||
|
||||
const port = process.env.PORT || 3001;
|
||||
const port = process.env.PORT || 3001
|
||||
|
||||
if (process.env.NODE_ENV !== "production") {
|
||||
dotenv.config({ path: ".env.local" });
|
||||
dotenv.config({ path: ".env.local" })
|
||||
}
|
||||
|
||||
const appExpress = express();
|
||||
const appExpress = express()
|
||||
|
||||
// Mount the github webhook middleware onto the express application
|
||||
appExpress.use(
|
||||
createNodeMiddleware(githubApp.webhooks, {path: webhookApiPath}),
|
||||
);
|
||||
appExpress.use(createNodeMiddleware(githubApp.webhooks, { path: webhookApiPath }))
|
||||
|
||||
appExpress.use(express.json());
|
||||
appExpress.use(express.json())
|
||||
|
||||
// Middleware to check for valid API key
|
||||
appExpress.use(async (req, res, next) => {
|
||||
const authHeader = req.headers.authorization;
|
||||
const authHeader = req.headers.authorization
|
||||
if (!authHeader) {
|
||||
return res.status(401).send("Authorization header is missing");
|
||||
return res.status(401).send("Authorization header is missing")
|
||||
}
|
||||
|
||||
const apiKey = authHeader.replace(/^Bearer\s+/, "");
|
||||
const userId = await userForAPIKey(apiKey);
|
||||
const apiKey = authHeader.replace(/^Bearer\s+/, "")
|
||||
const userId = await userForAPIKey(apiKey)
|
||||
if (userId == null) {
|
||||
return res.status(403).send("Invalid API key");
|
||||
return res.status(403).send("Invalid API key")
|
||||
}
|
||||
req.userId = userId;
|
||||
req.userId = userId
|
||||
|
||||
next();
|
||||
});
|
||||
next()
|
||||
})
|
||||
|
||||
appExpress.post("/api/suggest-pr-changes", async (req, res) => {
|
||||
try {
|
||||
const { owner, repo, pullNumber, diffContents, prComment } = req.body;
|
||||
const userId = req.userId;
|
||||
const { owner, repo, pullNumber, diffContents, prComment } = req.body
|
||||
const userId = req.userId
|
||||
|
||||
if (!repo || !owner || !pullNumber) {
|
||||
return res.status(400).send("Missing required fields");
|
||||
return res.status(400).send("Missing required fields")
|
||||
}
|
||||
|
||||
const nickname = await userNickname(userId);
|
||||
const nickname = await userNickname(userId)
|
||||
if (!nickname) {
|
||||
return res.status(401).send("Unauthorized"); //Error getting user nickname
|
||||
return res.status(401).send("Unauthorized") //Error getting user nickname
|
||||
}
|
||||
|
||||
const repoInstallation =
|
||||
await githubApp.octokit.rest.apps.getRepoInstallation({
|
||||
owner: owner,
|
||||
repo: repo,
|
||||
});
|
||||
const installationId = repoInstallation.data.id;
|
||||
const installationOctokit =
|
||||
await githubApp.getInstallationOctokit(installationId);
|
||||
const repoInstallation = await githubApp.octokit.rest.apps.getRepoInstallation({
|
||||
owner: owner,
|
||||
repo: repo,
|
||||
})
|
||||
const installationId = repoInstallation.data.id
|
||||
const installationOctokit = await githubApp.getInstallationOctokit(installationId)
|
||||
|
||||
try {
|
||||
// Check if the user is a collaborator on the repository
|
||||
|
|
@ -73,23 +69,22 @@ appExpress.post("/api/suggest-pr-changes", async (req, res) => {
|
|||
owner: owner,
|
||||
repo: repo,
|
||||
username: nickname,
|
||||
});
|
||||
})
|
||||
// If the request is successful, the user is a collaborator and we can continue
|
||||
if (response.status == 204) {
|
||||
console.log(`${nickname} is a collaborator on ${owner}/${repo}`);
|
||||
console.log(`${nickname} is a collaborator on ${owner}/${repo}`)
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.status === 404) {
|
||||
return res.status(401).send("Unauthorized"); // User is not a collaborator
|
||||
return res.status(401).send("Unauthorized") // User is not a collaborator
|
||||
}
|
||||
// Handle other potential errors
|
||||
return res.status(500).send(`Error`); // Error checking collaborator status
|
||||
return res.status(500).send(`Error`) // Error checking collaborator status
|
||||
}
|
||||
|
||||
// Suggest changes
|
||||
|
||||
const diffContentsMap: Map<string, FileDiffContent> =
|
||||
objectToMap(diffContents);
|
||||
const diffContentsMap: Map<string, FileDiffContent> = objectToMap(diffContents)
|
||||
|
||||
const { validHunks, invalidHunks } = await determineValidHunks(
|
||||
installationOctokit.rest,
|
||||
|
|
@ -97,7 +92,7 @@ appExpress.post("/api/suggest-pr-changes", async (req, res) => {
|
|||
pullNumber,
|
||||
100,
|
||||
diffContentsMap,
|
||||
);
|
||||
)
|
||||
|
||||
if (invalidHunks.size > 0) {
|
||||
// we can't suggest all of the hunks for this PR, because some of them are invalid (out of scope for this PR).
|
||||
|
|
@ -110,7 +105,7 @@ appExpress.post("/api/suggest-pr-changes", async (req, res) => {
|
|||
repo,
|
||||
pullNumber,
|
||||
diffContentsMap,
|
||||
);
|
||||
)
|
||||
|
||||
// Add explanation comment
|
||||
const result = await installationOctokit.rest.issues.createComment({
|
||||
|
|
@ -118,10 +113,10 @@ appExpress.post("/api/suggest-pr-changes", async (req, res) => {
|
|||
repo: repo,
|
||||
issue_number: newPrData.data.number,
|
||||
body: prComment,
|
||||
});
|
||||
})
|
||||
|
||||
// Respond with the new PR details
|
||||
res.json(newPrData.data.number);
|
||||
res.json(newPrData.data.number)
|
||||
} else {
|
||||
// all suggestions are valid and can be made on this PR.
|
||||
// let's make the suggestions
|
||||
|
|
@ -135,7 +130,7 @@ appExpress.post("/api/suggest-pr-changes", async (req, res) => {
|
|||
pullNumber,
|
||||
pageSize: 100,
|
||||
},
|
||||
);
|
||||
)
|
||||
|
||||
// Add explanation comment
|
||||
const result = await installationOctokit.rest.issues.createComment({
|
||||
|
|
@ -143,13 +138,13 @@ appExpress.post("/api/suggest-pr-changes", async (req, res) => {
|
|||
repo: repo,
|
||||
issue_number: pullNumber,
|
||||
body: prComment,
|
||||
});
|
||||
res.json(reviewNumber);
|
||||
})
|
||||
res.json(reviewNumber)
|
||||
}
|
||||
} catch (error) {
|
||||
res.status(500).send(`Error creating pull request: ${error.message}`);
|
||||
res.status(500).send(`Error creating pull request: ${error.message}`)
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
// WORKS without express.json() vvvvv
|
||||
// const router = express.Router();
|
||||
|
|
@ -172,6 +167,6 @@ appExpress.post("/api/suggest-pr-changes", async (req, res) => {
|
|||
appExpress.listen(port, () => {
|
||||
console.log(
|
||||
`Server is listening on ${port} for CF API requests. Github App webhook events mounted at ${webhookApiPath}`,
|
||||
);
|
||||
console.log("Press Ctrl + C to quit.");
|
||||
});
|
||||
)
|
||||
console.log("Press Ctrl + C to quit.")
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,18 +1,18 @@
|
|||
import fs from 'fs';
|
||||
import fs from "fs"
|
||||
|
||||
const APP_ID: string = process.env.APP_ID || ''; // Replace with your GitHub App ID
|
||||
const APP_ID: string = process.env.APP_ID || "" // Replace with your GitHub App ID
|
||||
|
||||
const PRIVATE_KEY: string = fs.readFileSync('codeflash-app.2023-11-04.private-key.pem', 'utf8');
|
||||
const CLIENT_ID: string = process.env.CLIENT_ID || ''; // Replace with your GitHub App client ID
|
||||
const CLIENT_SECRET: string = process.env.CLIENT_SECRET || ''; // Replace with your GitHub App client secret
|
||||
const PRIVATE_KEY: string = fs.readFileSync("codeflash-app.2023-11-04.private-key.pem", "utf8")
|
||||
const CLIENT_ID: string = process.env.CLIENT_ID || "" // Replace with your GitHub App client ID
|
||||
const CLIENT_SECRET: string = process.env.CLIENT_SECRET || "" // Replace with your GitHub App client secret
|
||||
|
||||
if (!APP_ID || !PRIVATE_KEY || !CLIENT_ID || !CLIENT_SECRET) {
|
||||
throw new Error('Environment variables are not set');
|
||||
throw new Error("Environment variables are not set")
|
||||
}
|
||||
|
||||
export async function createWorkflow(repoOwner: string, repoName: string) {
|
||||
|
||||
const content = Buffer.from(`
|
||||
const content = Buffer.from(
|
||||
`
|
||||
name: Code Optimization
|
||||
|
||||
on:
|
||||
|
|
@ -28,16 +28,17 @@ jobs:
|
|||
- uses: CodeFlash/optimize-action@main
|
||||
with:
|
||||
token:
|
||||
`).toString('base64');
|
||||
`,
|
||||
).toString("base64")
|
||||
|
||||
await installationOctokit.repos.createOrUpdateFileContents({
|
||||
owner: repoOwner,
|
||||
repo: repoName,
|
||||
path: '.github/workflows/optimize.yml',
|
||||
message: 'Setup Code Optimization action',
|
||||
content: content,
|
||||
});
|
||||
await installationOctokit.repos.createOrUpdateFileContents({
|
||||
owner: repoOwner,
|
||||
repo: repoName,
|
||||
path: ".github/workflows/optimize.yml",
|
||||
message: "Setup Code Optimization action",
|
||||
content: content,
|
||||
})
|
||||
}
|
||||
|
||||
// Example usage
|
||||
createWorkflow('user-or-org', 'repo-name', 123456); // Replace with actual owner, repo name, and installation ID
|
||||
createWorkflow("user-or-org", "repo-name", 123456) // Replace with actual owner, repo name, and installation ID
|
||||
|
|
|
|||
|
|
@ -30,4 +30,4 @@
|
|||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,31 +1,38 @@
|
|||
import {FileDiffContent, Hunk} from "code-suggester/build/src/types";
|
||||
import { FileDiffContent, Hunk } from "code-suggester/build/src/types"
|
||||
|
||||
import {parseAllHunks} from "code-suggester/build/src/utils/diff-utils";
|
||||
import {getRawSuggestionHunks, partitionSuggestedHunksByScope} from "code-suggester/build/src/utils/hunk-utils";
|
||||
import {getPullRequestHunks} from "code-suggester/build/src/github/review-pull-request";
|
||||
import { parseAllHunks } from "code-suggester/build/src/utils/diff-utils"
|
||||
import {
|
||||
getRawSuggestionHunks,
|
||||
partitionSuggestedHunksByScope,
|
||||
} from "code-suggester/build/src/utils/hunk-utils"
|
||||
import { getPullRequestHunks } from "code-suggester/build/src/github/review-pull-request"
|
||||
|
||||
export function objectToMap(obj): Map<string, FileDiffContent> {
|
||||
const map = new Map();
|
||||
Object.keys(obj).forEach(key => {
|
||||
if (obj[key] && typeof obj[key] === 'object') {
|
||||
map.set(key, <FileDiffContent>{
|
||||
oldContent: obj[key].oldContent,
|
||||
newContent: obj[key].newContent
|
||||
});
|
||||
}
|
||||
});
|
||||
return map;
|
||||
const map = new Map()
|
||||
Object.keys(obj).forEach((key) => {
|
||||
if (obj[key] && typeof obj[key] === "object") {
|
||||
map.set(key, <FileDiffContent>{
|
||||
oldContent: obj[key].oldContent,
|
||||
newContent: obj[key].newContent,
|
||||
})
|
||||
}
|
||||
})
|
||||
return map
|
||||
}
|
||||
|
||||
export async function determineValidHunks(octokit, remote, pullNumber, pageSize, diffContents) {
|
||||
// get the hunks from the pull request
|
||||
const pullRequestHunks = await getPullRequestHunks(octokit, remote, pullNumber, pageSize);
|
||||
// get the hunks from the suggested change
|
||||
const allSuggestedHunks: Map<string, Hunk[]> = typeof diffContents === 'string'
|
||||
? parseAllHunks(diffContents)
|
||||
: getRawSuggestionHunks(diffContents);
|
||||
// split hunks by commentable and uncommentable
|
||||
const {validHunks, invalidHunks} = partitionSuggestedHunksByScope(pullRequestHunks, allSuggestedHunks);
|
||||
// get the hunks from the pull request
|
||||
const pullRequestHunks = await getPullRequestHunks(octokit, remote, pullNumber, pageSize)
|
||||
// get the hunks from the suggested change
|
||||
const allSuggestedHunks: Map<string, Hunk[]> =
|
||||
typeof diffContents === "string"
|
||||
? parseAllHunks(diffContents)
|
||||
: getRawSuggestionHunks(diffContents)
|
||||
// split hunks by commentable and uncommentable
|
||||
const { validHunks, invalidHunks } = partitionSuggestedHunksByScope(
|
||||
pullRequestHunks,
|
||||
allSuggestedHunks,
|
||||
)
|
||||
|
||||
return {validHunks, invalidHunks}
|
||||
return { validHunks, invalidHunks }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,4 +24,4 @@ module.exports = {
|
|||
rules: {
|
||||
"react/react-in-jsx-scope": "off",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1,4 @@
|
|||
{}
|
||||
printWidth: 100
|
||||
semi: false
|
||||
trailingComma: all
|
||||
bracketSameLine: false
|
||||
|
|
|
|||
|
|
@ -13,4 +13,4 @@
|
|||
"components": "@/components",
|
||||
"utils": "@/lib/utils"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,28 +1,25 @@
|
|||
// app/api/auth/[auth0]/route.js
|
||||
import { handleAuth, handleLogin, handleLogout } from '@auth0/nextjs-auth0'
|
||||
import { handleAuth, handleLogin, handleLogout } from "@auth0/nextjs-auth0"
|
||||
|
||||
export const GET = handleAuth({
|
||||
login: async (request: any, response: any) => {
|
||||
console.log('Logging in')
|
||||
console.log("Logging in")
|
||||
try {
|
||||
return await handleLogin(request, response, {
|
||||
returnTo: '/app'
|
||||
|
||||
returnTo: "/app",
|
||||
})
|
||||
} catch (error) {
|
||||
console.log('Error logging in:', error)
|
||||
console.log("Error logging in:", error)
|
||||
}
|
||||
},
|
||||
logout: async (request: any, response: any) => {
|
||||
console.log('Logging out')
|
||||
console.log("Logging out")
|
||||
try {
|
||||
return await handleLogout(request, response, {
|
||||
returnTo: '/login'
|
||||
|
||||
returnTo: "/login",
|
||||
})
|
||||
} catch (error) {
|
||||
console.log('Error logging out:', error)
|
||||
console.log("Error logging out:", error)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
export default function InstallationPage() {
|
||||
return (
|
||||
<>
|
||||
<div>Installation</div>
|
||||
</>
|
||||
)
|
||||
return (
|
||||
<>
|
||||
<div>Installation</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,23 +1,15 @@
|
|||
import { Sidebar } from '@/components/dashboard/sidebar'
|
||||
import { Sidebar } from "@/components/dashboard/sidebar"
|
||||
|
||||
export default function AppRootLayout ({
|
||||
children
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
export default function AppRootLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<>
|
||||
<div className="flex ">
|
||||
<div className=" max-w-xs">
|
||||
|
||||
<Sidebar className="h-screen border-r"/>
|
||||
{/* <text>CODEFLASH!</text> */}
|
||||
|
||||
</div>
|
||||
<div className="px-4 py-4">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
<>
|
||||
<div className="flex ">
|
||||
<div className=" max-w-xs">
|
||||
<Sidebar className="h-screen border-r" />
|
||||
{/* <text>CODEFLASH!</text> */}
|
||||
</div>
|
||||
<div className="px-4 py-4">{children}</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { redirect } from "next/navigation";
|
||||
import { redirect } from "next/navigation"
|
||||
|
||||
export default async function AppHomePage() {
|
||||
redirect("/app/install");
|
||||
redirect("/app/install")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,18 @@
|
|||
'use client'
|
||||
import {Button} from '@/components/ui/button'
|
||||
import {Trash2} from 'lucide-react'
|
||||
import {deleteAPIKey} from '@/app/app/tokens/tokenfuncs'
|
||||
"use client"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Trash2 } from "lucide-react"
|
||||
import { deleteAPIKey } from "@/app/app/tokens/tokenfuncs"
|
||||
|
||||
export function DeleteTokenButton ({ tokenId }: { tokenId: number }): JSX.Element {
|
||||
export function DeleteTokenButton({ tokenId }: { tokenId: number }): JSX.Element {
|
||||
return (
|
||||
<form onSubmit={(event) => {
|
||||
event.preventDefault()
|
||||
deleteAPIKey(tokenId)
|
||||
}}>
|
||||
<form
|
||||
onSubmit={(event) => {
|
||||
event.preventDefault()
|
||||
deleteAPIKey(tokenId)
|
||||
}}
|
||||
>
|
||||
<Button className="icon" variant="ghost" type="submit">
|
||||
<Trash2 size={12}/>
|
||||
<Trash2 size={12} />
|
||||
</Button>
|
||||
</form>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
"use client";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useForm } from "react-hook-form";
|
||||
"use client"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { useForm } from "react-hook-form"
|
||||
import {
|
||||
Dialog,
|
||||
DialogClose,
|
||||
|
|
@ -10,49 +10,49 @@ import {
|
|||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Form, FormControl, FormField } from "@/components/ui/form";
|
||||
import { Icons } from "@/components/icons";
|
||||
import * as z from "zod";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useToast } from "@/components/ui/use-toast";
|
||||
import React from "react";
|
||||
import { generateToken } from "@/app/app/tokens/tokenfuncs";
|
||||
import { Plus } from "lucide-react";
|
||||
} from "@/components/ui/dialog"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Label } from "@/components/ui/label"
|
||||
import { Form, FormControl, FormField } from "@/components/ui/form"
|
||||
import { Icons } from "@/components/icons"
|
||||
import * as z from "zod"
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { useToast } from "@/components/ui/use-toast"
|
||||
import React from "react"
|
||||
import { generateToken } from "@/app/app/tokens/tokenfuncs"
|
||||
import { Plus } from "lucide-react"
|
||||
|
||||
const formSchema = z.object({
|
||||
keyname: z.string().max(255, { message: "Key name must be a shorter name" }),
|
||||
});
|
||||
})
|
||||
|
||||
export function CreateTokenDialog(): React.JSX.Element {
|
||||
const [isResultDialogOpen, setIsResultDialogOpen] = React.useState(false);
|
||||
const [token, setToken] = React.useState("");
|
||||
const [keyname, setKeyname] = React.useState(""); // Only for dialog display purposes
|
||||
const { toast } = useToast();
|
||||
const [isResultDialogOpen, setIsResultDialogOpen] = React.useState(false)
|
||||
const [token, setToken] = React.useState("")
|
||||
const [keyname, setKeyname] = React.useState("") // Only for dialog display purposes
|
||||
const { toast } = useToast()
|
||||
|
||||
const form = useForm<z.infer<typeof formSchema>>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: {
|
||||
keyname: "Secret Key",
|
||||
},
|
||||
});
|
||||
})
|
||||
async function onSubmit(values: z.infer<typeof formSchema>): void {
|
||||
const token: string = await generateToken(values.keyname);
|
||||
setToken(token);
|
||||
setKeyname(values.keyname);
|
||||
setIsResultDialogOpen(true);
|
||||
const token: string = await generateToken(values.keyname)
|
||||
setToken(token)
|
||||
setKeyname(values.keyname)
|
||||
setIsResultDialogOpen(true)
|
||||
}
|
||||
|
||||
async function copyToClipboard(data: string): Promise<void> {
|
||||
try {
|
||||
await navigator.clipboard.writeText(data);
|
||||
await navigator.clipboard.writeText(data)
|
||||
toast({
|
||||
description: "API Key Copied to Clipboard!",
|
||||
});
|
||||
})
|
||||
} catch (err) {
|
||||
console.error("Failed to copy: ", err);
|
||||
console.error("Failed to copy: ", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -112,9 +112,9 @@ export function CreateTokenDialog(): React.JSX.Element {
|
|||
<DialogTitle>New API key created</DialogTitle>
|
||||
</DialogHeader>
|
||||
<DialogDescription>
|
||||
Please save this secret key somewhere safe and accessible. For
|
||||
security reasons, you won't be able to view it again. If you lose
|
||||
this secret key, you'll need to generate a new one.
|
||||
Please save this secret key somewhere safe and accessible. For security reasons, you
|
||||
won't be able to view it again. If you lose this secret key, you'll need to generate a
|
||||
new one.
|
||||
</DialogDescription>
|
||||
<div className="gap-4 py-4">
|
||||
<div className="grid grid-rows-2 items-center">
|
||||
|
|
@ -133,7 +133,7 @@ export function CreateTokenDialog(): React.JSX.Element {
|
|||
size="icon"
|
||||
className="float-right ml-5 "
|
||||
onClick={async (event) => {
|
||||
await copyToClipboard(token);
|
||||
await copyToClipboard(token)
|
||||
}}
|
||||
>
|
||||
<Icons.copy />
|
||||
|
|
@ -145,8 +145,8 @@ export function CreateTokenDialog(): React.JSX.Element {
|
|||
<DialogClose asChild>
|
||||
<form
|
||||
onSubmit={(event) => {
|
||||
setIsResultDialogOpen(false);
|
||||
event.preventDefault();
|
||||
setIsResultDialogOpen(false)
|
||||
event.preventDefault()
|
||||
}}
|
||||
>
|
||||
<Button type="submit">Done</Button>
|
||||
|
|
@ -156,5 +156,5 @@ export function CreateTokenDialog(): React.JSX.Element {
|
|||
</DialogContent>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { type cf_api_keys, PrismaClient } from "@prisma/client";
|
||||
import { type cf_api_keys, PrismaClient } from "@prisma/client"
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
|
|
@ -6,27 +6,27 @@ import {
|
|||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import { getSession } from "@auth0/nextjs-auth0";
|
||||
import { redirect } from "next/navigation";
|
||||
import { CreateTokenDialog } from "@/app/app/tokens/dialog-create-token";
|
||||
import { DeleteTokenButton } from "@/app/app/tokens/delete-token-button";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
} from "@/components/ui/table"
|
||||
import ReactMarkdown from "react-markdown"
|
||||
import { getSession } from "@auth0/nextjs-auth0"
|
||||
import { redirect } from "next/navigation"
|
||||
import { CreateTokenDialog } from "@/app/app/tokens/dialog-create-token"
|
||||
import { DeleteTokenButton } from "@/app/app/tokens/delete-token-button"
|
||||
import { Separator } from "@/components/ui/separator"
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
const prisma = new PrismaClient()
|
||||
|
||||
export default async function APITokenGenerator(): Promise<JSX.Element> {
|
||||
const user = await getSession();
|
||||
const user = await getSession()
|
||||
if (user == null) {
|
||||
redirect("/login");
|
||||
redirect("/login")
|
||||
}
|
||||
const userId = user.user.sub;
|
||||
const userId = user.user.sub
|
||||
const apiKeys: cf_api_keys[] = await prisma.cf_api_keys.findMany({
|
||||
where: { user_id: userId },
|
||||
});
|
||||
})
|
||||
// {/* TODO: Query Auth0 for user's organizations */}
|
||||
const organizations = ["CodeFlash AI", "Another Organization"];
|
||||
const organizations = ["CodeFlash AI", "Another Organization"]
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
|
@ -35,8 +35,8 @@ export default async function APITokenGenerator(): Promise<JSX.Element> {
|
|||
</h3>
|
||||
<Separator />
|
||||
<p className="leading-7 [&:not(:first-child)]:mt-6">
|
||||
Your secret API keys are listed below. Please note that we do not
|
||||
display your secret API keys again after you generate them.
|
||||
Your secret API keys are listed below. Please note that we do not display your secret API
|
||||
keys again after you generate them.
|
||||
</p>
|
||||
<Table className="mt-6">
|
||||
<TableHeader>
|
||||
|
|
@ -54,17 +54,11 @@ export default async function APITokenGenerator(): Promise<JSX.Element> {
|
|||
<TableCell className="py-0">{key.name}</TableCell>
|
||||
{/* Display the key using a Markdown component */}
|
||||
<TableCell className="py-0">
|
||||
<ReactMarkdown>{`\`\`\`cf-${"*".repeat(3)}${
|
||||
key.suffix
|
||||
}\`\`\``}</ReactMarkdown>
|
||||
<ReactMarkdown>{`\`\`\`cf-${"*".repeat(3)}${key.suffix}\`\`\``}</ReactMarkdown>
|
||||
</TableCell>
|
||||
<TableCell className="py-0">{key.created_at.toLocaleString()}</TableCell>
|
||||
<TableCell className="py-0">
|
||||
{key.created_at.toLocaleString()}
|
||||
</TableCell>
|
||||
<TableCell className="py-0">
|
||||
{key.last_used != null
|
||||
? key.last_used.toLocaleString()
|
||||
: "Never"}
|
||||
{key.last_used != null ? key.last_used.toLocaleString() : "Never"}
|
||||
</TableCell>
|
||||
<TableCell className="py-0">
|
||||
<DeleteTokenButton tokenId={key.id} />
|
||||
|
|
@ -89,5 +83,5 @@ export default async function APITokenGenerator(): Promise<JSX.Element> {
|
|||
{/* </Select> */}
|
||||
{/* </div> */}
|
||||
</div>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
'use server'
|
||||
import { getSession } from '@auth0/nextjs-auth0'
|
||||
import { redirect } from 'next/navigation'
|
||||
import { deleteAPIKeyById, genAndStoreAPITokenHash } from 'common/token-functions'
|
||||
"use server"
|
||||
import { getSession } from "@auth0/nextjs-auth0"
|
||||
import { redirect } from "next/navigation"
|
||||
import { deleteAPIKeyById, genAndStoreAPITokenHash } from "common/token-functions"
|
||||
|
||||
export async function generateToken (keyName: string): Promise<string> {
|
||||
export async function generateToken(keyName: string): Promise<string> {
|
||||
const user = await getSession()
|
||||
if (user == null) {
|
||||
redirect('/login')
|
||||
redirect("/login")
|
||||
}
|
||||
const userId = user.user.sub
|
||||
|
||||
|
|
@ -14,10 +14,10 @@ export async function generateToken (keyName: string): Promise<string> {
|
|||
return token
|
||||
}
|
||||
|
||||
export async function deleteAPIKey (id: number): Promise<void> {
|
||||
export async function deleteAPIKey(id: number): Promise<void> {
|
||||
const user = await getSession()
|
||||
if (user == null) {
|
||||
redirect('/login')
|
||||
redirect("/login")
|
||||
return
|
||||
}
|
||||
const userId = user.user.sub
|
||||
|
|
|
|||
|
|
@ -3,75 +3,75 @@
|
|||
@tailwind utilities;
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 222.2 84% 4.9%;
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 222.2 84% 4.9%;
|
||||
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 222.2 84% 4.9%;
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 222.2 84% 4.9%;
|
||||
|
||||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 222.2 84% 4.9%;
|
||||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 222.2 84% 4.9%;
|
||||
|
||||
--primary: 222.2 47.4% 11.2%;
|
||||
--primary-foreground: 210 40% 98%;
|
||||
--primary: 222.2 47.4% 11.2%;
|
||||
--primary-foreground: 210 40% 98%;
|
||||
|
||||
--secondary: 210 40% 96.1%;
|
||||
--secondary-foreground: 222.2 47.4% 11.2%;
|
||||
--secondary: 210 40% 96.1%;
|
||||
--secondary-foreground: 222.2 47.4% 11.2%;
|
||||
|
||||
--muted: 210 40% 96.1%;
|
||||
--muted-foreground: 215.4 16.3% 46.9%;
|
||||
--muted: 210 40% 96.1%;
|
||||
--muted-foreground: 215.4 16.3% 46.9%;
|
||||
|
||||
--accent: 210 40% 96.1%;
|
||||
--accent-foreground: 222.2 47.4% 11.2%;
|
||||
--accent: 210 40% 96.1%;
|
||||
--accent-foreground: 222.2 47.4% 11.2%;
|
||||
|
||||
--destructive: 0 84.2% 60.2%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
--destructive: 0 84.2% 60.2%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
|
||||
--border: 214.3 31.8% 91.4%;
|
||||
--input: 214.3 31.8% 91.4%;
|
||||
--ring: 222.2 84% 4.9%;
|
||||
--border: 214.3 31.8% 91.4%;
|
||||
--input: 214.3 31.8% 91.4%;
|
||||
--ring: 222.2 84% 4.9%;
|
||||
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: 222.2 84% 4.9%;
|
||||
--foreground: 210 40% 98%;
|
||||
.dark {
|
||||
--background: 222.2 84% 4.9%;
|
||||
--foreground: 210 40% 98%;
|
||||
|
||||
--card: 222.2 84% 4.9%;
|
||||
--card-foreground: 210 40% 98%;
|
||||
--card: 222.2 84% 4.9%;
|
||||
--card-foreground: 210 40% 98%;
|
||||
|
||||
--popover: 222.2 84% 4.9%;
|
||||
--popover-foreground: 210 40% 98%;
|
||||
--popover: 222.2 84% 4.9%;
|
||||
--popover-foreground: 210 40% 98%;
|
||||
|
||||
--primary: 210 40% 98%;
|
||||
--primary-foreground: 222.2 47.4% 11.2%;
|
||||
--primary: 210 40% 98%;
|
||||
--primary-foreground: 222.2 47.4% 11.2%;
|
||||
|
||||
--secondary: 217.2 32.6% 17.5%;
|
||||
--secondary-foreground: 210 40% 98%;
|
||||
--secondary: 217.2 32.6% 17.5%;
|
||||
--secondary-foreground: 210 40% 98%;
|
||||
|
||||
--muted: 217.2 32.6% 17.5%;
|
||||
--muted-foreground: 215 20.2% 65.1%;
|
||||
--muted: 217.2 32.6% 17.5%;
|
||||
--muted-foreground: 215 20.2% 65.1%;
|
||||
|
||||
--accent: 217.2 32.6% 17.5%;
|
||||
--accent-foreground: 210 40% 98%;
|
||||
--accent: 217.2 32.6% 17.5%;
|
||||
--accent-foreground: 210 40% 98%;
|
||||
|
||||
--destructive: 0 62.8% 30.6%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
--destructive: 0 62.8% 30.6%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
|
||||
--border: 217.2 32.6% 17.5%;
|
||||
--input: 217.2 32.6% 17.5%;
|
||||
--ring: 212.7 26.8% 83.9%;
|
||||
}
|
||||
--border: 217.2 32.6% 17.5%;
|
||||
--input: 217.2 32.6% 17.5%;
|
||||
--ring: 212.7 26.8% 83.9%;
|
||||
}
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border;
|
||||
}
|
||||
* {
|
||||
@apply border-border;
|
||||
}
|
||||
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,49 +1,39 @@
|
|||
import type { Metadata } from 'next'
|
||||
import { Inter as FontSans } from 'next/font/google'
|
||||
import './globals.css'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { ThemeProvider } from '@/components/theme-provider'
|
||||
import { UserProvider } from '@auth0/nextjs-auth0/client'
|
||||
import { Toaster } from '@/components/ui/toaster'
|
||||
import type { Metadata } from "next"
|
||||
import { Inter as FontSans } from "next/font/google"
|
||||
import "./globals.css"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { ThemeProvider } from "@/components/theme-provider"
|
||||
import { UserProvider } from "@auth0/nextjs-auth0/client"
|
||||
import { Toaster } from "@/components/ui/toaster"
|
||||
|
||||
const fontSans = FontSans({
|
||||
subsets: ['latin'],
|
||||
variable: '--font-sans'
|
||||
subsets: ["latin"],
|
||||
variable: "--font-sans",
|
||||
})
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Create Next App',
|
||||
description: 'Generated by create next app'
|
||||
title: "Create Next App",
|
||||
description: "Generated by create next app",
|
||||
}
|
||||
|
||||
export default function RootLayout ({
|
||||
children
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body className={cn(
|
||||
'min-h-screen bg-background font-sans antialiased',
|
||||
fontSans.variable
|
||||
)}>
|
||||
<html lang="en">
|
||||
<body className={cn("min-h-screen bg-background font-sans antialiased", fontSans.variable)}>
|
||||
<UserProvider>
|
||||
|
||||
<ThemeProvider
|
||||
attribute="class"
|
||||
defaultTheme="system"
|
||||
enableSystem
|
||||
disableTransitionOnChange
|
||||
>
|
||||
<div className="flex flex-col justify-between w-full h-full min-h-screen">
|
||||
<main className="flex-auto w-full max-w-none mx-auto">
|
||||
{children}
|
||||
</main>
|
||||
<Toaster />
|
||||
</div>
|
||||
</ThemeProvider>
|
||||
</UserProvider>
|
||||
</body>
|
||||
</html>
|
||||
<ThemeProvider
|
||||
attribute="class"
|
||||
defaultTheme="system"
|
||||
enableSystem
|
||||
disableTransitionOnChange
|
||||
>
|
||||
<div className="flex flex-col justify-between w-full h-full min-h-screen">
|
||||
<main className="flex-auto w-full max-w-none mx-auto">{children}</main>
|
||||
<Toaster />
|
||||
</div>
|
||||
</ThemeProvider>
|
||||
</UserProvider>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
"use client";
|
||||
import Link from "next/link";
|
||||
import { UserAuthForm } from "@/components/user-auth-form";
|
||||
import { redirect } from "next/navigation";
|
||||
import { Icons } from "@/components/icons";
|
||||
import { useUser } from "@auth0/nextjs-auth0/client";
|
||||
"use client"
|
||||
import Link from "next/link"
|
||||
import { UserAuthForm } from "@/components/user-auth-form"
|
||||
import { redirect } from "next/navigation"
|
||||
import { Icons } from "@/components/icons"
|
||||
import { useUser } from "@auth0/nextjs-auth0/client"
|
||||
|
||||
// export const metadata: Metadata = {
|
||||
// title: 'CodeFlash login',
|
||||
|
|
@ -11,13 +11,13 @@ import { useUser } from "@auth0/nextjs-auth0/client";
|
|||
// }
|
||||
|
||||
export default function AuthenticationPage(): JSX.Element {
|
||||
const { user, error, isLoading } = useUser();
|
||||
const { user, error, isLoading } = useUser()
|
||||
|
||||
if (isLoading) return <div>Loading...</div>;
|
||||
if (error) return <div>{error.message}</div>;
|
||||
if (isLoading) return <div>Loading...</div>
|
||||
if (error) return <div>{error.message}</div>
|
||||
if (user) {
|
||||
console.log("user logged in!", user.name);
|
||||
redirect("/app");
|
||||
console.log("user logged in!", user.name)
|
||||
redirect("/app")
|
||||
} else {
|
||||
return (
|
||||
<>
|
||||
|
|
@ -42,24 +42,16 @@ export default function AuthenticationPage(): JSX.Element {
|
|||
<div className="lg:p-8">
|
||||
<div className="mx-auto flex w-full flex-col justify-center space-y-6 sm:w-[350px]">
|
||||
<div className="flex flex-col space-y-2 text-center">
|
||||
<h1 className="text-2xl font-semibold tracking-tight">
|
||||
Start using CodeFlash
|
||||
</h1>
|
||||
<h1 className="text-2xl font-semibold tracking-tight">Start using CodeFlash</h1>
|
||||
</div>
|
||||
<UserAuthForm />
|
||||
<p className="px-8 text-center text-sm text-muted-foreground">
|
||||
By clicking continue, you agree to our{" "}
|
||||
<Link
|
||||
href="/terms"
|
||||
className="underline underline-offset-4 hover:text-primary"
|
||||
>
|
||||
<Link href="/terms" className="underline underline-offset-4 hover:text-primary">
|
||||
Terms of Service
|
||||
</Link>{" "}
|
||||
and{" "}
|
||||
<Link
|
||||
href="/privacy"
|
||||
className="underline underline-offset-4 hover:text-primary"
|
||||
>
|
||||
<Link href="/privacy" className="underline underline-offset-4 hover:text-primary">
|
||||
Privacy Policy
|
||||
</Link>
|
||||
.
|
||||
|
|
@ -68,6 +60,6 @@ export default function AuthenticationPage(): JSX.Element {
|
|||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
export default function LogoBox () {
|
||||
export default function LogoBox() {
|
||||
return (
|
||||
<div className="block h-11">
|
||||
<div className="grid grid-cols-2">
|
||||
{/* <Icons.codeflash_logo/> */}
|
||||
<h2 className="scroll-m-20 pb-2 text-3xl font-semibold tracking-tight first:mt-0 pl-8">
|
||||
CodeFlash
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div className="block h-11">
|
||||
<div className="grid grid-cols-2">
|
||||
{/* <Icons.codeflash_logo/> */}
|
||||
<h2 className="scroll-m-20 pb-2 text-3xl font-semibold tracking-tight first:mt-0 pl-8">
|
||||
CodeFlash
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
"use client";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import LogoBox from "@/components/dashboard/logo-box";
|
||||
import { SignOut } from "@/components/ui/SignOut";
|
||||
import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
import React from "react";
|
||||
import { KeyRound } from "lucide-react";
|
||||
"use client"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import LogoBox from "@/components/dashboard/logo-box"
|
||||
import { SignOut } from "@/components/ui/SignOut"
|
||||
import Link from "next/link"
|
||||
import { usePathname } from "next/navigation"
|
||||
import React from "react"
|
||||
import { KeyRound } from "lucide-react"
|
||||
|
||||
export function Sidebar({ className }: any) {
|
||||
const currentRoute = usePathname();
|
||||
const currentRoute = usePathname()
|
||||
return (
|
||||
<div className={cn("flex flex-col h-screen pt-3 pb-6 max-w-xs", className)}>
|
||||
<LogoBox />
|
||||
|
|
@ -18,9 +18,7 @@ export function Sidebar({ className }: any) {
|
|||
<div className="space-y-2 grid gap-y-1">
|
||||
<Link href="/app/install">
|
||||
<Button
|
||||
variant={
|
||||
currentRoute === "/app/install" ? "secondary" : "ghost"
|
||||
}
|
||||
variant={currentRoute === "/app/install" ? "secondary" : "ghost"}
|
||||
className="w-full justify-start"
|
||||
>
|
||||
<svg
|
||||
|
|
@ -55,5 +53,5 @@ export function Sidebar({ className }: any) {
|
|||
<SignOut className="w-28 items-center mx-auto" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,44 +2,58 @@ type IconProps = React.HTMLAttributes<SVGElement>
|
|||
|
||||
export const Icons = {
|
||||
github: (props: IconProps) => (
|
||||
<svg viewBox="0 0 438.549 438.549" {...props}>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M409.132 114.573c-19.608-33.596-46.205-60.194-79.798-79.8-33.598-19.607-70.277-29.408-110.063-29.408-39.781 0-76.472 9.804-110.063 29.408-33.596 19.605-60.192 46.204-79.8 79.8C9.803 148.168 0 184.854 0 224.63c0 47.78 13.94 90.745 41.827 128.906 27.884 38.164 63.906 64.572 108.063 79.227 5.14.954 8.945.283 11.419-1.996 2.475-2.282 3.711-5.14 3.711-8.562 0-.571-.049-5.708-.144-15.417a2549.81 2549.81 0 01-.144-25.406l-6.567 1.136c-4.187.767-9.469 1.092-15.846 1-6.374-.089-12.991-.757-19.842-1.999-6.854-1.231-13.229-4.086-19.13-8.559-5.898-4.473-10.085-10.328-12.56-17.556l-2.855-6.57c-1.903-4.374-4.899-9.233-8.992-14.559-4.093-5.331-8.232-8.945-12.419-10.848l-1.999-1.431c-1.332-.951-2.568-2.098-3.711-3.429-1.142-1.331-1.997-2.663-2.568-3.997-.572-1.335-.098-2.43 1.427-3.289 1.525-.859 4.281-1.276 8.28-1.276l5.708.853c3.807.763 8.516 3.042 14.133 6.851 5.614 3.806 10.229 8.754 13.846 14.842 4.38 7.806 9.657 13.754 15.846 17.847 6.184 4.093 12.419 6.136 18.699 6.136 6.28 0 11.704-.476 16.274-1.423 4.565-.952 8.848-2.383 12.847-4.285 1.713-12.758 6.377-22.559 13.988-29.41-10.848-1.14-20.601-2.857-29.264-5.14-8.658-2.286-17.605-5.996-26.835-11.14-9.235-5.137-16.896-11.516-22.985-19.126-6.09-7.614-11.088-17.61-14.987-29.979-3.901-12.374-5.852-26.648-5.852-42.826 0-23.035 7.52-42.637 22.557-58.817-7.044-17.318-6.379-36.732 1.997-58.24 5.52-1.715 13.706-.428 24.554 3.853 10.85 4.283 18.794 7.952 23.84 10.994 5.046 3.041 9.089 5.618 12.135 7.708 17.705-4.947 35.976-7.421 54.818-7.421s37.117 2.474 54.823 7.421l10.849-6.849c7.419-4.57 16.18-8.758 26.262-12.565 10.088-3.805 17.802-4.853 23.134-3.138 8.562 21.509 9.325 40.922 2.279 58.24 15.036 16.18 22.559 35.787 22.559 58.817 0 16.178-1.958 30.497-5.853 42.966-3.9 12.471-8.941 22.457-15.125 29.979-6.191 7.521-13.901 13.85-23.131 18.986-9.232 5.14-18.182 8.85-26.84 11.136-8.662 2.286-18.415 4.004-29.263 5.146 9.894 8.562 14.842 22.077 14.842 40.539v60.237c0 3.422 1.19 6.279 3.572 8.562 2.379 2.279 6.136 2.95 11.276 1.995 44.163-14.653 80.185-41.062 108.068-79.226 27.88-38.161 41.825-81.126 41.825-128.906-.01-39.771-9.818-76.454-29.414-110.049z"
|
||||
></path>
|
||||
</svg>
|
||||
<svg viewBox="0 0 438.549 438.549" {...props}>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M409.132 114.573c-19.608-33.596-46.205-60.194-79.798-79.8-33.598-19.607-70.277-29.408-110.063-29.408-39.781 0-76.472 9.804-110.063 29.408-33.596 19.605-60.192 46.204-79.8 79.8C9.803 148.168 0 184.854 0 224.63c0 47.78 13.94 90.745 41.827 128.906 27.884 38.164 63.906 64.572 108.063 79.227 5.14.954 8.945.283 11.419-1.996 2.475-2.282 3.711-5.14 3.711-8.562 0-.571-.049-5.708-.144-15.417a2549.81 2549.81 0 01-.144-25.406l-6.567 1.136c-4.187.767-9.469 1.092-15.846 1-6.374-.089-12.991-.757-19.842-1.999-6.854-1.231-13.229-4.086-19.13-8.559-5.898-4.473-10.085-10.328-12.56-17.556l-2.855-6.57c-1.903-4.374-4.899-9.233-8.992-14.559-4.093-5.331-8.232-8.945-12.419-10.848l-1.999-1.431c-1.332-.951-2.568-2.098-3.711-3.429-1.142-1.331-1.997-2.663-2.568-3.997-.572-1.335-.098-2.43 1.427-3.289 1.525-.859 4.281-1.276 8.28-1.276l5.708.853c3.807.763 8.516 3.042 14.133 6.851 5.614 3.806 10.229 8.754 13.846 14.842 4.38 7.806 9.657 13.754 15.846 17.847 6.184 4.093 12.419 6.136 18.699 6.136 6.28 0 11.704-.476 16.274-1.423 4.565-.952 8.848-2.383 12.847-4.285 1.713-12.758 6.377-22.559 13.988-29.41-10.848-1.14-20.601-2.857-29.264-5.14-8.658-2.286-17.605-5.996-26.835-11.14-9.235-5.137-16.896-11.516-22.985-19.126-6.09-7.614-11.088-17.61-14.987-29.979-3.901-12.374-5.852-26.648-5.852-42.826 0-23.035 7.52-42.637 22.557-58.817-7.044-17.318-6.379-36.732 1.997-58.24 5.52-1.715 13.706-.428 24.554 3.853 10.85 4.283 18.794 7.952 23.84 10.994 5.046 3.041 9.089 5.618 12.135 7.708 17.705-4.947 35.976-7.421 54.818-7.421s37.117 2.474 54.823 7.421l10.849-6.849c7.419-4.57 16.18-8.758 26.262-12.565 10.088-3.805 17.802-4.853 23.134-3.138 8.562 21.509 9.325 40.922 2.279 58.24 15.036 16.18 22.559 35.787 22.559 58.817 0 16.178-1.958 30.497-5.853 42.966-3.9 12.471-8.941 22.457-15.125 29.979-6.191 7.521-13.901 13.85-23.131 18.986-9.232 5.14-18.182 8.85-26.84 11.136-8.662 2.286-18.415 4.004-29.263 5.146 9.894 8.562 14.842 22.077 14.842 40.539v60.237c0 3.422 1.19 6.279 3.572 8.562 2.379 2.279 6.136 2.95 11.276 1.995 44.163-14.653 80.185-41.062 108.068-79.226 27.88-38.161 41.825-81.126 41.825-128.906-.01-39.771-9.818-76.454-29.414-110.049z"
|
||||
></path>
|
||||
</svg>
|
||||
),
|
||||
spinner: (props: IconProps) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
{...props}
|
||||
>
|
||||
<path d="M21 12a9 9 0 1 1-6.219-8.56"/>
|
||||
</svg>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
{...props}
|
||||
>
|
||||
<path d="M21 12a9 9 0 1 1-6.219-8.56" />
|
||||
</svg>
|
||||
),
|
||||
codeflash_logo: (props: IconProps) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className="mr-2 h-6 w-6"
|
||||
>
|
||||
<path d="M15 6v12a3 3 0 1 0 3-3H6a3 3 0 1 0 3 3V6a3 3 0 1 0-3 3h12a3 3 0 1 0-3-3"/>
|
||||
</svg>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className="mr-2 h-6 w-6"
|
||||
>
|
||||
<path d="M15 6v12a3 3 0 1 0 3-3H6a3 3 0 1 0 3 3V6a3 3 0 1 0-3 3h12a3 3 0 1 0-3-3" />
|
||||
</svg>
|
||||
),
|
||||
copy: (props: IconProps) => (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="lucide lucide-copy"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/></svg>
|
||||
)
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className="lucide lucide-copy"
|
||||
>
|
||||
<rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
|
||||
<path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
|
||||
</svg>
|
||||
),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,14 @@
|
|||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import {useEffect, useState} from "react"
|
||||
import {ThemeProvider as NextThemesProvider} from "next-themes"
|
||||
import {type ThemeProviderProps} from "next-themes/dist/types"
|
||||
import { useEffect, useState } from "react"
|
||||
import { ThemeProvider as NextThemesProvider } from "next-themes"
|
||||
import { type ThemeProviderProps } from "next-themes/dist/types"
|
||||
|
||||
export function ThemeProvider({children, ...props}: ThemeProviderProps) {
|
||||
const [isClient, setIsClient] = useState(false);
|
||||
useEffect(() => {
|
||||
setIsClient(true);
|
||||
}, []);
|
||||
return (isClient ? <NextThemesProvider {...props}>{children}</NextThemesProvider>
|
||||
: <>{children}</>
|
||||
)
|
||||
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
|
||||
const [isClient, setIsClient] = useState(false)
|
||||
useEffect(() => {
|
||||
setIsClient(true)
|
||||
}, [])
|
||||
return isClient ? <NextThemesProvider {...props}>{children}</NextThemesProvider> : <>{children}</>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,18 @@
|
|||
'use client'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Icons } from '@/components/icons'
|
||||
import * as React from 'react'
|
||||
"use client"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Icons } from "@/components/icons"
|
||||
import * as React from "react"
|
||||
|
||||
export function SignIn ({
|
||||
export function SignIn({
|
||||
provider,
|
||||
...props
|
||||
}: { provider?: string } & React.ComponentPropsWithRef<typeof Button>) {
|
||||
return (
|
||||
<a href="/api/auth/login">
|
||||
<Button {...props}>
|
||||
<Icons.github className="mr-2 h-4 w-4"/>
|
||||
Sign In with GitHub
|
||||
</Button>
|
||||
</a>
|
||||
<a href="/api/auth/login">
|
||||
<Button {...props}>
|
||||
<Icons.github className="mr-2 h-4 w-4" />
|
||||
Sign In with GitHub
|
||||
</Button>
|
||||
</a>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
'use client'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { cn } from '@/lib/utils'
|
||||
"use client"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
export function SignOut ({ props, className }) {
|
||||
export function SignOut({ props, className }) {
|
||||
return (
|
||||
<a href="/api/auth/logout">
|
||||
<Button className={cn('', className)} {...props}>
|
||||
Sign Out
|
||||
</Button>
|
||||
</a>
|
||||
<a href="/api/auth/logout">
|
||||
<Button className={cn("", className)} {...props}>
|
||||
Sign Out
|
||||
</Button>
|
||||
</a>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,56 +1,49 @@
|
|||
import * as React from "react"
|
||||
import {Slot} from "@radix-ui/react-slot"
|
||||
import {cva, type VariantProps} from "class-variance-authority"
|
||||
import { Slot } from "@radix-ui/react-slot"
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
|
||||
import {cn} from "@/lib/utils"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const buttonVariants = cva(
|
||||
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
||||
destructive:
|
||||
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
||||
outline:
|
||||
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
||||
secondary:
|
||||
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||
ghost: "hover:bg-accent hover:text-accent-foreground",
|
||||
link: "text-primary underline-offset-4 hover:underline",
|
||||
},
|
||||
size: {
|
||||
default: "h-10 px-4 py-2",
|
||||
sm: "h-9 rounded-md px-3",
|
||||
lg: "h-11 rounded-md px-8",
|
||||
icon: "h-10 w-10",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
size: "default",
|
||||
},
|
||||
}
|
||||
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
||||
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
||||
outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
||||
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||
ghost: "hover:bg-accent hover:text-accent-foreground",
|
||||
link: "text-primary underline-offset-4 hover:underline",
|
||||
},
|
||||
size: {
|
||||
default: "h-10 px-4 py-2",
|
||||
sm: "h-9 rounded-md px-3",
|
||||
lg: "h-11 rounded-md px-8",
|
||||
icon: "h-10 w-10",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
size: "default",
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
export interface ButtonProps
|
||||
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||
VariantProps<typeof buttonVariants> {
|
||||
asChild?: boolean
|
||||
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||
VariantProps<typeof buttonVariants> {
|
||||
asChild?: boolean
|
||||
}
|
||||
|
||||
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
({className, variant, size, asChild = false, ...props}, ref) => {
|
||||
const Comp = asChild ? Slot : "button"
|
||||
return (
|
||||
<Comp
|
||||
className={cn(buttonVariants({variant, size, className}))}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
({ className, variant, size, asChild = false, ...props }, ref) => {
|
||||
const Comp = asChild ? Slot : "button"
|
||||
return (
|
||||
<Comp className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props} />
|
||||
)
|
||||
},
|
||||
)
|
||||
Button.displayName = "Button"
|
||||
|
||||
export {Button, buttonVariants}
|
||||
export { Button, buttonVariants }
|
||||
|
|
|
|||
|
|
@ -1,79 +1,56 @@
|
|||
import * as React from "react"
|
||||
|
||||
import {cn} from "@/lib/utils"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const Card = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({className, ...props}, ref) => (
|
||||
const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
||||
({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"rounded-lg border bg-card text-card-foreground shadow-sm",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
ref={ref}
|
||||
className={cn("rounded-lg border bg-card text-card-foreground shadow-sm", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
),
|
||||
)
|
||||
Card.displayName = "Card"
|
||||
|
||||
const CardHeader = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({className, ...props}, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn("flex flex-col space-y-1.5 p-6", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
const CardHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
||||
({ className, ...props }, ref) => (
|
||||
<div ref={ref} className={cn("flex flex-col space-y-1.5 p-6", className)} {...props} />
|
||||
),
|
||||
)
|
||||
CardHeader.displayName = "CardHeader"
|
||||
|
||||
const CardTitle = React.forwardRef<
|
||||
HTMLParagraphElement,
|
||||
React.HTMLAttributes<HTMLHeadingElement>
|
||||
>(({className, ...props}, ref) => (
|
||||
const CardTitle = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLHeadingElement>>(
|
||||
({ className, ...props }, ref) => (
|
||||
<h3
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"text-2xl font-semibold leading-none tracking-tight",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
ref={ref}
|
||||
className={cn("text-2xl font-semibold leading-none tracking-tight", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
),
|
||||
)
|
||||
CardTitle.displayName = "CardTitle"
|
||||
|
||||
const CardDescription = React.forwardRef<
|
||||
HTMLParagraphElement,
|
||||
React.HTMLAttributes<HTMLParagraphElement>
|
||||
>(({className, ...props}, ref) => (
|
||||
<p
|
||||
ref={ref}
|
||||
className={cn("text-sm text-muted-foreground", className)}
|
||||
{...props}
|
||||
/>
|
||||
HTMLParagraphElement,
|
||||
React.HTMLAttributes<HTMLParagraphElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<p ref={ref} className={cn("text-sm text-muted-foreground", className)} {...props} />
|
||||
))
|
||||
CardDescription.displayName = "CardDescription"
|
||||
|
||||
const CardContent = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({className, ...props}, ref) => (
|
||||
const CardContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
||||
({ className, ...props }, ref) => (
|
||||
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
|
||||
))
|
||||
),
|
||||
)
|
||||
CardContent.displayName = "CardContent"
|
||||
|
||||
const CardFooter = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({className, ...props}, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn("flex items-center p-6 pt-0", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
const CardFooter = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
||||
({ className, ...props }, ref) => (
|
||||
<div ref={ref} className={cn("flex items-center p-6 pt-0", className)} {...props} />
|
||||
),
|
||||
)
|
||||
CardFooter.displayName = "CardFooter"
|
||||
|
||||
export {Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent}
|
||||
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ const DialogOverlay = React.forwardRef<
|
|||
ref={ref}
|
||||
className={cn(
|
||||
"fixed inset-0 z-50 bg-background/80 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
|
|
@ -39,7 +39,7 @@ const DialogContent = React.forwardRef<
|
|||
ref={ref}
|
||||
className={cn(
|
||||
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
|
|
@ -53,29 +53,14 @@ const DialogContent = React.forwardRef<
|
|||
))
|
||||
DialogContent.displayName = DialogPrimitive.Content.displayName
|
||||
|
||||
const DialogHeader = ({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-col space-y-1.5 text-center sm:text-left",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
const DialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div className={cn("flex flex-col space-y-1.5 text-center sm:text-left", className)} {...props} />
|
||||
)
|
||||
DialogHeader.displayName = "DialogHeader"
|
||||
|
||||
const DialogFooter = ({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
const DialogFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
|
||||
className
|
||||
)}
|
||||
className={cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
|
|
@ -87,10 +72,7 @@ const DialogTitle = React.forwardRef<
|
|||
>(({ className, ...props }, ref) => (
|
||||
<DialogPrimitive.Title
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"text-lg font-semibold leading-none tracking-tight",
|
||||
className
|
||||
)}
|
||||
className={cn("text-lg font-semibold leading-none tracking-tight", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
|
|
|
|||
|
|
@ -17,18 +17,16 @@ const Form = FormProvider
|
|||
|
||||
type FormFieldContextValue<
|
||||
TFieldValues extends FieldValues = FieldValues,
|
||||
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
|
||||
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
||||
> = {
|
||||
name: TName
|
||||
}
|
||||
|
||||
const FormFieldContext = React.createContext<FormFieldContextValue>(
|
||||
{} as FormFieldContextValue
|
||||
)
|
||||
const FormFieldContext = React.createContext<FormFieldContextValue>({} as FormFieldContextValue)
|
||||
|
||||
const FormField = <
|
||||
TFieldValues extends FieldValues = FieldValues,
|
||||
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
|
||||
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
||||
>({
|
||||
...props
|
||||
}: ControllerProps<TFieldValues, TName>) => {
|
||||
|
|
@ -66,22 +64,19 @@ type FormItemContextValue = {
|
|||
id: string
|
||||
}
|
||||
|
||||
const FormItemContext = React.createContext<FormItemContextValue>(
|
||||
{} as FormItemContextValue
|
||||
const FormItemContext = React.createContext<FormItemContextValue>({} as FormItemContextValue)
|
||||
|
||||
const FormItem = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
||||
({ className, ...props }, ref) => {
|
||||
const id = React.useId()
|
||||
|
||||
return (
|
||||
<FormItemContext.Provider value={{ id }}>
|
||||
<div ref={ref} className={cn("space-y-2", className)} {...props} />
|
||||
</FormItemContext.Provider>
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
const FormItem = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => {
|
||||
const id = React.useId()
|
||||
|
||||
return (
|
||||
<FormItemContext.Provider value={{ id }}>
|
||||
<div ref={ref} className={cn("space-y-2", className)} {...props} />
|
||||
</FormItemContext.Provider>
|
||||
)
|
||||
})
|
||||
FormItem.displayName = "FormItem"
|
||||
|
||||
const FormLabel = React.forwardRef<
|
||||
|
|
@ -111,11 +106,7 @@ const FormControl = React.forwardRef<
|
|||
<Slot
|
||||
ref={ref}
|
||||
id={formItemId}
|
||||
aria-describedby={
|
||||
!error
|
||||
? `${formDescriptionId}`
|
||||
: `${formDescriptionId} ${formMessageId}`
|
||||
}
|
||||
aria-describedby={!error ? `${formDescriptionId}` : `${formDescriptionId} ${formMessageId}`}
|
||||
aria-invalid={!!error}
|
||||
{...props}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,26 +1,24 @@
|
|||
import * as React from "react"
|
||||
|
||||
import {cn} from "@/lib/utils"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
export interface InputProps
|
||||
extends React.InputHTMLAttributes<HTMLInputElement> {
|
||||
}
|
||||
export interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {}
|
||||
|
||||
const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
||||
({className, type, ...props}, ref) => {
|
||||
return (
|
||||
<input
|
||||
type={type}
|
||||
className={cn(
|
||||
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className
|
||||
)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
({ className, type, ...props }, ref) => {
|
||||
return (
|
||||
<input
|
||||
type={type}
|
||||
className={cn(
|
||||
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className,
|
||||
)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
},
|
||||
)
|
||||
Input.displayName = "Input"
|
||||
|
||||
export {Input}
|
||||
export { Input }
|
||||
|
|
|
|||
|
|
@ -2,25 +2,20 @@
|
|||
|
||||
import * as React from "react"
|
||||
import * as LabelPrimitive from "@radix-ui/react-label"
|
||||
import {cva, type VariantProps} from "class-variance-authority"
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
|
||||
import {cn} from "@/lib/utils"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const labelVariants = cva(
|
||||
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
|
||||
)
|
||||
|
||||
const Label = React.forwardRef<
|
||||
React.ElementRef<typeof LabelPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
|
||||
VariantProps<typeof labelVariants>
|
||||
>(({className, ...props}, ref) => (
|
||||
<LabelPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn(labelVariants(), className)}
|
||||
{...props}
|
||||
/>
|
||||
React.ElementRef<typeof LabelPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> & VariantProps<typeof labelVariants>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<LabelPrimitive.Root ref={ref} className={cn(labelVariants(), className)} {...props} />
|
||||
))
|
||||
Label.displayName = LabelPrimitive.Root.displayName
|
||||
|
||||
export {Label}
|
||||
export { Label }
|
||||
|
|
|
|||
|
|
@ -1,129 +1,121 @@
|
|||
"use client"
|
||||
import * as React from "react"
|
||||
import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu"
|
||||
import {cva} from "class-variance-authority"
|
||||
import {ChevronDown} from "lucide-react"
|
||||
import { cva } from "class-variance-authority"
|
||||
import { ChevronDown } from "lucide-react"
|
||||
|
||||
import {cn} from "@/lib/utils"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const NavigationMenu = React.forwardRef<
|
||||
React.ElementRef<typeof NavigationMenuPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Root>
|
||||
>(({className, children, ...props}, ref) => (
|
||||
<NavigationMenuPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative z-10 flex max-w-max flex-1 items-center justify-center",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<NavigationMenuViewport/>
|
||||
</NavigationMenuPrimitive.Root>
|
||||
React.ElementRef<typeof NavigationMenuPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Root>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<NavigationMenuPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn("relative z-10 flex max-w-max flex-1 items-center justify-center", className)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<NavigationMenuViewport />
|
||||
</NavigationMenuPrimitive.Root>
|
||||
))
|
||||
NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName
|
||||
|
||||
const NavigationMenuList = React.forwardRef<
|
||||
React.ElementRef<typeof NavigationMenuPrimitive.List>,
|
||||
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.List>
|
||||
>(({className, ...props}, ref) => (
|
||||
<NavigationMenuPrimitive.List
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"group flex flex-1 list-none items-center justify-center space-x-1",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
React.ElementRef<typeof NavigationMenuPrimitive.List>,
|
||||
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.List>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<NavigationMenuPrimitive.List
|
||||
ref={ref}
|
||||
className={cn("group flex flex-1 list-none items-center justify-center space-x-1", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName
|
||||
|
||||
const NavigationMenuItem = NavigationMenuPrimitive.Item
|
||||
|
||||
const navigationMenuTriggerStyle = cva(
|
||||
"group inline-flex h-10 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50"
|
||||
"group inline-flex h-10 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50",
|
||||
)
|
||||
|
||||
const NavigationMenuTrigger = React.forwardRef<
|
||||
React.ElementRef<typeof NavigationMenuPrimitive.Trigger>,
|
||||
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Trigger>
|
||||
>(({className, children, ...props}, ref) => (
|
||||
<NavigationMenuPrimitive.Trigger
|
||||
ref={ref}
|
||||
className={cn(navigationMenuTriggerStyle(), "group", className)}
|
||||
{...props}
|
||||
>
|
||||
{children}{" "}
|
||||
<ChevronDown
|
||||
className="relative top-[1px] ml-1 h-3 w-3 transition duration-200 group-data-[state=open]:rotate-180"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</NavigationMenuPrimitive.Trigger>
|
||||
React.ElementRef<typeof NavigationMenuPrimitive.Trigger>,
|
||||
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Trigger>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<NavigationMenuPrimitive.Trigger
|
||||
ref={ref}
|
||||
className={cn(navigationMenuTriggerStyle(), "group", className)}
|
||||
{...props}
|
||||
>
|
||||
{children}{" "}
|
||||
<ChevronDown
|
||||
className="relative top-[1px] ml-1 h-3 w-3 transition duration-200 group-data-[state=open]:rotate-180"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</NavigationMenuPrimitive.Trigger>
|
||||
))
|
||||
NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName
|
||||
|
||||
const NavigationMenuContent = React.forwardRef<
|
||||
React.ElementRef<typeof NavigationMenuPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Content>
|
||||
>(({className, ...props}, ref) => (
|
||||
<NavigationMenuPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"left-0 top-0 w-full data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 md:absolute md:w-auto ",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
React.ElementRef<typeof NavigationMenuPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Content>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<NavigationMenuPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"left-0 top-0 w-full data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 md:absolute md:w-auto ",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName
|
||||
|
||||
const NavigationMenuLink = NavigationMenuPrimitive.Link
|
||||
|
||||
const NavigationMenuViewport = React.forwardRef<
|
||||
React.ElementRef<typeof NavigationMenuPrimitive.Viewport>,
|
||||
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Viewport>
|
||||
>(({className, ...props}, ref) => (
|
||||
<div className={cn("absolute left-0 top-full flex justify-center")}>
|
||||
<NavigationMenuPrimitive.Viewport
|
||||
className={cn(
|
||||
"origin-top-center relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 md:w-[var(--radix-navigation-menu-viewport-width)]",
|
||||
className
|
||||
)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
React.ElementRef<typeof NavigationMenuPrimitive.Viewport>,
|
||||
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Viewport>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div className={cn("absolute left-0 top-full flex justify-center")}>
|
||||
<NavigationMenuPrimitive.Viewport
|
||||
className={cn(
|
||||
"origin-top-center relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 md:w-[var(--radix-navigation-menu-viewport-width)]",
|
||||
className,
|
||||
)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
))
|
||||
NavigationMenuViewport.displayName =
|
||||
NavigationMenuPrimitive.Viewport.displayName
|
||||
NavigationMenuViewport.displayName = NavigationMenuPrimitive.Viewport.displayName
|
||||
|
||||
const NavigationMenuIndicator = React.forwardRef<
|
||||
React.ElementRef<typeof NavigationMenuPrimitive.Indicator>,
|
||||
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Indicator>
|
||||
>(({className, ...props}, ref) => (
|
||||
<NavigationMenuPrimitive.Indicator
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<div className="relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm bg-border shadow-md"/>
|
||||
</NavigationMenuPrimitive.Indicator>
|
||||
React.ElementRef<typeof NavigationMenuPrimitive.Indicator>,
|
||||
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Indicator>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<NavigationMenuPrimitive.Indicator
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<div className="relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm bg-border shadow-md" />
|
||||
</NavigationMenuPrimitive.Indicator>
|
||||
))
|
||||
NavigationMenuIndicator.displayName =
|
||||
NavigationMenuPrimitive.Indicator.displayName
|
||||
NavigationMenuIndicator.displayName = NavigationMenuPrimitive.Indicator.displayName
|
||||
|
||||
export {
|
||||
navigationMenuTriggerStyle,
|
||||
NavigationMenu,
|
||||
NavigationMenuList,
|
||||
NavigationMenuItem,
|
||||
NavigationMenuContent,
|
||||
NavigationMenuTrigger,
|
||||
NavigationMenuLink,
|
||||
NavigationMenuIndicator,
|
||||
NavigationMenuViewport,
|
||||
navigationMenuTriggerStyle,
|
||||
NavigationMenu,
|
||||
NavigationMenuList,
|
||||
NavigationMenuItem,
|
||||
NavigationMenuContent,
|
||||
NavigationMenuTrigger,
|
||||
NavigationMenuLink,
|
||||
NavigationMenuIndicator,
|
||||
NavigationMenuViewport,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
'use client'
|
||||
"use client"
|
||||
|
||||
import * as React from 'react'
|
||||
import * as SelectPrimitive from '@radix-ui/react-select'
|
||||
import {Check, ChevronDown, ChevronUp} from 'lucide-react'
|
||||
import * as React from "react"
|
||||
import * as SelectPrimitive from "@radix-ui/react-select"
|
||||
import { Check, ChevronDown, ChevronUp } from "lucide-react"
|
||||
|
||||
import {cn} from '@/lib/utils'
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const Select = SelectPrimitive.Root
|
||||
|
||||
|
|
@ -13,136 +13,129 @@ const SelectGroup = SelectPrimitive.Group
|
|||
const SelectValue = SelectPrimitive.Value
|
||||
|
||||
const SelectTrigger = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Trigger>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
|
||||
>(({className, children, ...props}, ref) => (
|
||||
<SelectPrimitive.Trigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<SelectPrimitive.Icon asChild>
|
||||
<ChevronDown className="h-4 w-4 opacity-50"/>
|
||||
</SelectPrimitive.Icon>
|
||||
</SelectPrimitive.Trigger>
|
||||
React.ElementRef<typeof SelectPrimitive.Trigger>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<SelectPrimitive.Trigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<SelectPrimitive.Icon asChild>
|
||||
<ChevronDown className="h-4 w-4 opacity-50" />
|
||||
</SelectPrimitive.Icon>
|
||||
</SelectPrimitive.Trigger>
|
||||
))
|
||||
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
|
||||
|
||||
const SelectScrollUpButton = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
|
||||
>(({className, ...props}, ref) => (
|
||||
<SelectPrimitive.ScrollUpButton
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'flex cursor-default items-center justify-center py-1',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ChevronUp className="h-4 w-4"/>
|
||||
</SelectPrimitive.ScrollUpButton>
|
||||
React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SelectPrimitive.ScrollUpButton
|
||||
ref={ref}
|
||||
className={cn("flex cursor-default items-center justify-center py-1", className)}
|
||||
{...props}
|
||||
>
|
||||
<ChevronUp className="h-4 w-4" />
|
||||
</SelectPrimitive.ScrollUpButton>
|
||||
))
|
||||
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
|
||||
|
||||
const SelectScrollDownButton = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
|
||||
>(({className, ...props}, ref) => (
|
||||
<SelectPrimitive.ScrollDownButton
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'flex cursor-default items-center justify-center py-1',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ChevronDown className="h-4 w-4"/>
|
||||
</SelectPrimitive.ScrollDownButton>
|
||||
React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SelectPrimitive.ScrollDownButton
|
||||
ref={ref}
|
||||
className={cn("flex cursor-default items-center justify-center py-1", className)}
|
||||
{...props}
|
||||
>
|
||||
<ChevronDown className="h-4 w-4" />
|
||||
</SelectPrimitive.ScrollDownButton>
|
||||
))
|
||||
SelectScrollDownButton.displayName =
|
||||
SelectPrimitive.ScrollDownButton.displayName
|
||||
SelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayName
|
||||
|
||||
const SelectContent = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
|
||||
>(({className, children, position = 'popper', ...props}, ref) => (
|
||||
<SelectPrimitive.Portal>
|
||||
<SelectPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
|
||||
position === 'popper' &&
|
||||
'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1',
|
||||
className
|
||||
)}
|
||||
position={position}
|
||||
{...props}
|
||||
React.ElementRef<typeof SelectPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
|
||||
>(({ className, children, position = "popper", ...props }, ref) => (
|
||||
<SelectPrimitive.Portal>
|
||||
<SelectPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||
position === "popper" &&
|
||||
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
||||
className,
|
||||
)}
|
||||
position={position}
|
||||
{...props}
|
||||
>
|
||||
<SelectScrollUpButton />
|
||||
<SelectPrimitive.Viewport
|
||||
className={cn(
|
||||
"p-1",
|
||||
position === "popper" &&
|
||||
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]",
|
||||
)}
|
||||
>
|
||||
<SelectScrollUpButton/>
|
||||
<SelectPrimitive.Viewport
|
||||
className={cn(
|
||||
'p-1',
|
||||
position === 'popper' &&
|
||||
'h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]'
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</SelectPrimitive.Viewport>
|
||||
<SelectScrollDownButton/>
|
||||
</SelectPrimitive.Content>
|
||||
</SelectPrimitive.Portal>
|
||||
{children}
|
||||
</SelectPrimitive.Viewport>
|
||||
<SelectScrollDownButton />
|
||||
</SelectPrimitive.Content>
|
||||
</SelectPrimitive.Portal>
|
||||
))
|
||||
SelectContent.displayName = SelectPrimitive.Content.displayName
|
||||
|
||||
const SelectLabel = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Label>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
|
||||
>(({className, ...props}, ref) => (
|
||||
<SelectPrimitive.Label
|
||||
ref={ref}
|
||||
className={cn('py-1.5 pl-8 pr-2 text-sm font-semibold', className)}
|
||||
{...props}
|
||||
/>
|
||||
React.ElementRef<typeof SelectPrimitive.Label>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SelectPrimitive.Label
|
||||
ref={ref}
|
||||
className={cn("py-1.5 pl-8 pr-2 text-sm font-semibold", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
SelectLabel.displayName = SelectPrimitive.Label.displayName
|
||||
|
||||
const SelectItem = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Item>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
|
||||
>(({className, children, ...props}, ref) => (
|
||||
<SelectPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
React.ElementRef<typeof SelectPrimitive.Item>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<SelectPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||
<SelectPrimitive.ItemIndicator>
|
||||
<Check className="h-4 w-4"/>
|
||||
<Check className="h-4 w-4" />
|
||||
</SelectPrimitive.ItemIndicator>
|
||||
</span>
|
||||
|
||||
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
||||
</SelectPrimitive.Item>
|
||||
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
||||
</SelectPrimitive.Item>
|
||||
))
|
||||
SelectItem.displayName = SelectPrimitive.Item.displayName
|
||||
|
||||
const SelectSeparator = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Separator>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
|
||||
>(({className, ...props}, ref) => (
|
||||
<SelectPrimitive.Separator
|
||||
ref={ref}
|
||||
className={cn('-mx-1 my-1 h-px bg-muted', className)}
|
||||
{...props}
|
||||
/>
|
||||
React.ElementRef<typeof SelectPrimitive.Separator>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SelectPrimitive.Separator
|
||||
ref={ref}
|
||||
className={cn("-mx-1 my-1 h-px bg-muted", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
SelectSeparator.displayName = SelectPrimitive.Separator.displayName
|
||||
|
||||
|
|
@ -156,5 +149,5 @@ export {
|
|||
SelectItem,
|
||||
SelectSeparator,
|
||||
SelectScrollUpButton,
|
||||
SelectScrollDownButton
|
||||
SelectScrollDownButton,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,24 +8,19 @@ import { cn } from "@/lib/utils"
|
|||
const Separator = React.forwardRef<
|
||||
React.ElementRef<typeof SeparatorPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
|
||||
>(
|
||||
(
|
||||
{ className, orientation = "horizontal", decorative = true, ...props },
|
||||
ref
|
||||
) => (
|
||||
<SeparatorPrimitive.Root
|
||||
ref={ref}
|
||||
decorative={decorative}
|
||||
orientation={orientation}
|
||||
className={cn(
|
||||
"shrink-0 bg-border",
|
||||
orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
)
|
||||
>(({ className, orientation = "horizontal", decorative = true, ...props }, ref) => (
|
||||
<SeparatorPrimitive.Root
|
||||
ref={ref}
|
||||
decorative={decorative}
|
||||
orientation={orientation}
|
||||
className={cn(
|
||||
"shrink-0 bg-border",
|
||||
orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
Separator.displayName = SeparatorPrimitive.Root.displayName
|
||||
|
||||
export { Separator }
|
||||
|
|
|
|||
|
|
@ -1,117 +1,91 @@
|
|||
import * as React from 'react'
|
||||
import * as React from "react"
|
||||
|
||||
import {cn} from '@/lib/utils'
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const Table = React.forwardRef<
|
||||
HTMLTableElement,
|
||||
React.HTMLAttributes<HTMLTableElement>
|
||||
>(({className, ...props}, ref) => (
|
||||
const Table = React.forwardRef<HTMLTableElement, React.HTMLAttributes<HTMLTableElement>>(
|
||||
({ className, ...props }, ref) => (
|
||||
<div className="relative w-full overflow-auto">
|
||||
<table
|
||||
ref={ref}
|
||||
className={cn('w-full caption-bottom text-sm', className)}
|
||||
{...props}
|
||||
/>
|
||||
<table ref={ref} className={cn("w-full caption-bottom text-sm", className)} {...props} />
|
||||
</div>
|
||||
))
|
||||
Table.displayName = 'Table'
|
||||
),
|
||||
)
|
||||
Table.displayName = "Table"
|
||||
|
||||
const TableHeader = React.forwardRef<
|
||||
HTMLTableSectionElement,
|
||||
React.HTMLAttributes<HTMLTableSectionElement>
|
||||
>(({className, ...props}, ref) => (
|
||||
<thead ref={ref} className={cn('[&_tr]:border-b', className)} {...props} />
|
||||
HTMLTableSectionElement,
|
||||
React.HTMLAttributes<HTMLTableSectionElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} />
|
||||
))
|
||||
TableHeader.displayName = 'TableHeader'
|
||||
TableHeader.displayName = "TableHeader"
|
||||
|
||||
const TableBody = React.forwardRef<
|
||||
HTMLTableSectionElement,
|
||||
React.HTMLAttributes<HTMLTableSectionElement>
|
||||
>(({className, ...props}, ref) => (
|
||||
<tbody
|
||||
ref={ref}
|
||||
className={cn('[&_tr:last-child]:border-0', className)}
|
||||
{...props}
|
||||
/>
|
||||
HTMLTableSectionElement,
|
||||
React.HTMLAttributes<HTMLTableSectionElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<tbody ref={ref} className={cn("[&_tr:last-child]:border-0", className)} {...props} />
|
||||
))
|
||||
TableBody.displayName = 'TableBody'
|
||||
TableBody.displayName = "TableBody"
|
||||
|
||||
const TableFooter = React.forwardRef<
|
||||
HTMLTableSectionElement,
|
||||
React.HTMLAttributes<HTMLTableSectionElement>
|
||||
>(({className, ...props}, ref) => (
|
||||
<tfoot
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'border-t bg-muted/50 font-medium [&>tr]:last:border-b-0',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
HTMLTableSectionElement,
|
||||
React.HTMLAttributes<HTMLTableSectionElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<tfoot
|
||||
ref={ref}
|
||||
className={cn("border-t bg-muted/50 font-medium [&>tr]:last:border-b-0", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
TableFooter.displayName = 'TableFooter'
|
||||
TableFooter.displayName = "TableFooter"
|
||||
|
||||
const TableRow = React.forwardRef<
|
||||
HTMLTableRowElement,
|
||||
React.HTMLAttributes<HTMLTableRowElement>
|
||||
>(({className, ...props}, ref) => (
|
||||
const TableRow = React.forwardRef<HTMLTableRowElement, React.HTMLAttributes<HTMLTableRowElement>>(
|
||||
({ className, ...props }, ref) => (
|
||||
<tr
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
TableRow.displayName = 'TableRow'
|
||||
),
|
||||
)
|
||||
TableRow.displayName = "TableRow"
|
||||
|
||||
const TableHead = React.forwardRef<
|
||||
HTMLTableCellElement,
|
||||
React.ThHTMLAttributes<HTMLTableCellElement>
|
||||
>(({className, ...props}, ref) => (
|
||||
<th
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
HTMLTableCellElement,
|
||||
React.ThHTMLAttributes<HTMLTableCellElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<th
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
TableHead.displayName = 'TableHead'
|
||||
TableHead.displayName = "TableHead"
|
||||
|
||||
const TableCell = React.forwardRef<
|
||||
HTMLTableCellElement,
|
||||
React.TdHTMLAttributes<HTMLTableCellElement>
|
||||
>(({className, ...props}, ref) => (
|
||||
<td
|
||||
ref={ref}
|
||||
className={cn('p-4 align-middle [&:has([role=checkbox])]:pr-0', className)}
|
||||
{...props}
|
||||
/>
|
||||
HTMLTableCellElement,
|
||||
React.TdHTMLAttributes<HTMLTableCellElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<td
|
||||
ref={ref}
|
||||
className={cn("p-4 align-middle [&:has([role=checkbox])]:pr-0", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
TableCell.displayName = 'TableCell'
|
||||
TableCell.displayName = "TableCell"
|
||||
|
||||
const TableCaption = React.forwardRef<
|
||||
HTMLTableCaptionElement,
|
||||
React.HTMLAttributes<HTMLTableCaptionElement>
|
||||
>(({className, ...props}, ref) => (
|
||||
<caption
|
||||
ref={ref}
|
||||
className={cn('mt-4 text-sm text-muted-foreground', className)}
|
||||
{...props}
|
||||
/>
|
||||
HTMLTableCaptionElement,
|
||||
React.HTMLAttributes<HTMLTableCaptionElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<caption ref={ref} className={cn("mt-4 text-sm text-muted-foreground", className)} {...props} />
|
||||
))
|
||||
TableCaption.displayName = 'TableCaption'
|
||||
TableCaption.displayName = "TableCaption"
|
||||
|
||||
export {
|
||||
Table,
|
||||
TableHeader,
|
||||
TableBody,
|
||||
TableFooter,
|
||||
TableHead,
|
||||
TableRow,
|
||||
TableCell,
|
||||
TableCaption
|
||||
}
|
||||
export { Table, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption }
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ const ToastViewport = React.forwardRef<
|
|||
ref={ref}
|
||||
className={cn(
|
||||
"fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
|
|
@ -35,13 +35,12 @@ const toastVariants = cva(
|
|||
defaultVariants: {
|
||||
variant: "default",
|
||||
},
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
const Toast = React.forwardRef<
|
||||
React.ElementRef<typeof ToastPrimitives.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> &
|
||||
VariantProps<typeof toastVariants>
|
||||
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> & VariantProps<typeof toastVariants>
|
||||
>(({ className, variant, ...props }, ref) => {
|
||||
return (
|
||||
<ToastPrimitives.Root
|
||||
|
|
@ -61,7 +60,7 @@ const ToastAction = React.forwardRef<
|
|||
ref={ref}
|
||||
className={cn(
|
||||
"inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium ring-offset-background transition-colors hover:bg-secondary focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
|
|
@ -76,7 +75,7 @@ const ToastClose = React.forwardRef<
|
|||
ref={ref}
|
||||
className={cn(
|
||||
"absolute right-2 top-2 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-2 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
toast-close=""
|
||||
{...props}
|
||||
|
|
@ -90,11 +89,7 @@ const ToastTitle = React.forwardRef<
|
|||
React.ElementRef<typeof ToastPrimitives.Title>,
|
||||
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Title>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<ToastPrimitives.Title
|
||||
ref={ref}
|
||||
className={cn("text-sm font-semibold", className)}
|
||||
{...props}
|
||||
/>
|
||||
<ToastPrimitives.Title ref={ref} className={cn("text-sm font-semibold", className)} {...props} />
|
||||
))
|
||||
ToastTitle.displayName = ToastPrimitives.Title.displayName
|
||||
|
||||
|
|
|
|||
|
|
@ -20,9 +20,7 @@ export function Toaster() {
|
|||
<Toast key={id} {...props}>
|
||||
<div className="grid gap-1">
|
||||
{title && <ToastTitle>{title}</ToastTitle>}
|
||||
{description && (
|
||||
<ToastDescription>{description}</ToastDescription>
|
||||
)}
|
||||
{description && <ToastDescription>{description}</ToastDescription>}
|
||||
</div>
|
||||
{action}
|
||||
<ToastClose />
|
||||
|
|
|
|||
|
|
@ -1,10 +1,7 @@
|
|||
// Inspired by react-hot-toast library
|
||||
import * as React from "react"
|
||||
|
||||
import type {
|
||||
ToastActionElement,
|
||||
ToastProps,
|
||||
} from "@/components/ui/toast"
|
||||
import type { ToastActionElement, ToastProps } from "@/components/ui/toast"
|
||||
|
||||
const TOAST_LIMIT = 1
|
||||
const TOAST_REMOVE_DELAY = 1000000
|
||||
|
|
@ -83,9 +80,7 @@ export const reducer = (state: State, action: Action): State => {
|
|||
case "UPDATE_TOAST":
|
||||
return {
|
||||
...state,
|
||||
toasts: state.toasts.map((t) =>
|
||||
t.id === action.toast.id ? { ...t, ...action.toast } : t
|
||||
),
|
||||
toasts: state.toasts.map((t) => (t.id === action.toast.id ? { ...t, ...action.toast } : t)),
|
||||
}
|
||||
|
||||
case "DISMISS_TOAST": {
|
||||
|
|
@ -109,7 +104,7 @@ export const reducer = (state: State, action: Action): State => {
|
|||
...t,
|
||||
open: false,
|
||||
}
|
||||
: t
|
||||
: t,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import * as React from "react";
|
||||
import * as React from "react"
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
import { SignIn } from "@/components/ui/SignIn";
|
||||
import { cn } from "@/lib/utils"
|
||||
import { SignIn } from "@/components/ui/SignIn"
|
||||
|
||||
interface UserAuthFormProps extends React.HTMLAttributes<HTMLDivElement> {}
|
||||
|
||||
|
|
@ -10,12 +10,10 @@ export function UserAuthForm({ className, ...props }: UserAuthFormProps) {
|
|||
<div className={cn("grid gap-6", className)} {...props}>
|
||||
<div className="relative">
|
||||
<div className="relative flex justify-center text-xs uppercase">
|
||||
<span className="bg-background px-2 text-muted-foreground">
|
||||
Sign In or sign up
|
||||
</span>
|
||||
<span className="bg-background px-2 text-muted-foreground">Sign In or sign up</span>
|
||||
</div>
|
||||
</div>
|
||||
<SignIn className="w-full" />
|
||||
</div>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import { PrismaClient } from '@prisma/client'
|
||||
import { PrismaClient } from "@prisma/client"
|
||||
|
||||
// Docs about instantiating `PrismaClient` with Next.js:
|
||||
// https://pris.ly/d/help/next-js-best-practices
|
||||
|
||||
let prisma: PrismaClient
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
if (process.env.NODE_ENV === "production") {
|
||||
prisma = new PrismaClient()
|
||||
} else {
|
||||
if (!global.prisma) {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { type ClassValue, clsx } from 'clsx'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import { type ClassValue, clsx } from "clsx"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
|
||||
export function cn (...inputs: ClassValue[]) {
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
// TODO: Could not make this work, this is preferred way. See if you can make it work.
|
||||
import { withMiddlewareAuthRequired } from '@auth0/nextjs-auth0/edge'
|
||||
import { withMiddlewareAuthRequired } from "@auth0/nextjs-auth0/edge"
|
||||
|
||||
export default withMiddlewareAuthRequired({
|
||||
returnTo: '/login'
|
||||
returnTo: "/login",
|
||||
})
|
||||
|
||||
export const config = {
|
||||
matcher: '/app/:path*'
|
||||
matcher: "/app/:path*",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,76 +1,76 @@
|
|||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
darkMode: ["class"],
|
||||
content: [
|
||||
'./pages/**/*.{ts,tsx}',
|
||||
'./components/**/*.{ts,tsx}',
|
||||
'./app/**/*.{ts,tsx}',
|
||||
'./src/**/*.{ts,tsx}',
|
||||
],
|
||||
theme: {
|
||||
container: {
|
||||
center: true,
|
||||
padding: "2rem",
|
||||
screens: {
|
||||
"2xl": "1400px",
|
||||
},
|
||||
},
|
||||
extend: {
|
||||
colors: {
|
||||
border: "hsl(var(--border))",
|
||||
input: "hsl(var(--input))",
|
||||
ring: "hsl(var(--ring))",
|
||||
background: "hsl(var(--background))",
|
||||
foreground: "hsl(var(--foreground))",
|
||||
primary: {
|
||||
DEFAULT: "hsl(var(--primary))",
|
||||
foreground: "hsl(var(--primary-foreground))",
|
||||
},
|
||||
secondary: {
|
||||
DEFAULT: "hsl(var(--secondary))",
|
||||
foreground: "hsl(var(--secondary-foreground))",
|
||||
},
|
||||
destructive: {
|
||||
DEFAULT: "hsl(var(--destructive))",
|
||||
foreground: "hsl(var(--destructive-foreground))",
|
||||
},
|
||||
muted: {
|
||||
DEFAULT: "hsl(var(--muted))",
|
||||
foreground: "hsl(var(--muted-foreground))",
|
||||
},
|
||||
accent: {
|
||||
DEFAULT: "hsl(var(--accent))",
|
||||
foreground: "hsl(var(--accent-foreground))",
|
||||
},
|
||||
popover: {
|
||||
DEFAULT: "hsl(var(--popover))",
|
||||
foreground: "hsl(var(--popover-foreground))",
|
||||
},
|
||||
card: {
|
||||
DEFAULT: "hsl(var(--card))",
|
||||
foreground: "hsl(var(--card-foreground))",
|
||||
},
|
||||
},
|
||||
borderRadius: {
|
||||
lg: "var(--radius)",
|
||||
md: "calc(var(--radius) - 2px)",
|
||||
sm: "calc(var(--radius) - 4px)",
|
||||
},
|
||||
keyframes: {
|
||||
"accordion-down": {
|
||||
from: {height: 0},
|
||||
to: {height: "var(--radix-accordion-content-height)"},
|
||||
},
|
||||
"accordion-up": {
|
||||
from: {height: "var(--radix-accordion-content-height)"},
|
||||
to: {height: 0},
|
||||
},
|
||||
},
|
||||
animation: {
|
||||
"accordion-down": "accordion-down 0.2s ease-out",
|
||||
"accordion-up": "accordion-up 0.2s ease-out",
|
||||
},
|
||||
},
|
||||
darkMode: ["class"],
|
||||
content: [
|
||||
"./pages/**/*.{ts,tsx}",
|
||||
"./components/**/*.{ts,tsx}",
|
||||
"./app/**/*.{ts,tsx}",
|
||||
"./src/**/*.{ts,tsx}",
|
||||
],
|
||||
theme: {
|
||||
container: {
|
||||
center: true,
|
||||
padding: "2rem",
|
||||
screens: {
|
||||
"2xl": "1400px",
|
||||
},
|
||||
},
|
||||
plugins: [require("tailwindcss-animate")],
|
||||
}
|
||||
extend: {
|
||||
colors: {
|
||||
border: "hsl(var(--border))",
|
||||
input: "hsl(var(--input))",
|
||||
ring: "hsl(var(--ring))",
|
||||
background: "hsl(var(--background))",
|
||||
foreground: "hsl(var(--foreground))",
|
||||
primary: {
|
||||
DEFAULT: "hsl(var(--primary))",
|
||||
foreground: "hsl(var(--primary-foreground))",
|
||||
},
|
||||
secondary: {
|
||||
DEFAULT: "hsl(var(--secondary))",
|
||||
foreground: "hsl(var(--secondary-foreground))",
|
||||
},
|
||||
destructive: {
|
||||
DEFAULT: "hsl(var(--destructive))",
|
||||
foreground: "hsl(var(--destructive-foreground))",
|
||||
},
|
||||
muted: {
|
||||
DEFAULT: "hsl(var(--muted))",
|
||||
foreground: "hsl(var(--muted-foreground))",
|
||||
},
|
||||
accent: {
|
||||
DEFAULT: "hsl(var(--accent))",
|
||||
foreground: "hsl(var(--accent-foreground))",
|
||||
},
|
||||
popover: {
|
||||
DEFAULT: "hsl(var(--popover))",
|
||||
foreground: "hsl(var(--popover-foreground))",
|
||||
},
|
||||
card: {
|
||||
DEFAULT: "hsl(var(--card))",
|
||||
foreground: "hsl(var(--card-foreground))",
|
||||
},
|
||||
},
|
||||
borderRadius: {
|
||||
lg: "var(--radius)",
|
||||
md: "calc(var(--radius) - 2px)",
|
||||
sm: "calc(var(--radius) - 4px)",
|
||||
},
|
||||
keyframes: {
|
||||
"accordion-down": {
|
||||
from: { height: 0 },
|
||||
to: { height: "var(--radix-accordion-content-height)" },
|
||||
},
|
||||
"accordion-up": {
|
||||
from: { height: "var(--radix-accordion-content-height)" },
|
||||
to: { height: 0 },
|
||||
},
|
||||
},
|
||||
animation: {
|
||||
"accordion-down": "accordion-down 0.2s ease-out",
|
||||
"accordion-up": "accordion-up 0.2s ease-out",
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [require("tailwindcss-animate")],
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,24 +1,23 @@
|
|||
import type {Config} from 'tailwindcss'
|
||||
import {fontFamily} from "tailwindcss/defaultTheme"
|
||||
import type { Config } from "tailwindcss"
|
||||
import { fontFamily } from "tailwindcss/defaultTheme"
|
||||
|
||||
const config: Config = {
|
||||
content: [
|
||||
'./src/pages/**/*.{js,ts,jsx,tsx,mdx}',
|
||||
'./src/components/**/*.{js,ts,jsx,tsx,mdx}',
|
||||
'./src/app/**/*.{js,ts,jsx,tsx,mdx}',
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
backgroundImage: {
|
||||
'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))',
|
||||
'gradient-conic':
|
||||
'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))',
|
||||
},
|
||||
fontFamily: {
|
||||
sans: ["var(--font-sans)", ...fontFamily.sans],
|
||||
}
|
||||
},
|
||||
content: [
|
||||
"./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
"./src/components/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
"./src/app/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
backgroundImage: {
|
||||
"gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
|
||||
"gradient-conic": "conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
|
||||
},
|
||||
fontFamily: {
|
||||
sans: ["var(--font-sans)", ...fontFamily.sans],
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
},
|
||||
plugins: [],
|
||||
}
|
||||
export default config
|
||||
|
|
|
|||
|
|
@ -1 +1,4 @@
|
|||
{}
|
||||
printWidth: 100
|
||||
semi: false
|
||||
trailingComma: all
|
||||
bracketSameLine: false
|
||||
|
|
|
|||
|
|
@ -1,30 +1,29 @@
|
|||
import {DefaultAzureCredential} from "@azure/identity";
|
||||
import {KeyVaultSecret, SecretClient} from "@azure/keyvault-secrets";
|
||||
import { DefaultAzureCredential } from "@azure/identity"
|
||||
import { KeyVaultSecret, SecretClient } from "@azure/keyvault-secrets"
|
||||
|
||||
const keyVaultName: string = process.env["KEY_VAULT_NAME"];
|
||||
if (!keyVaultName) throw new Error("KEY_VAULT_NAME is empty");
|
||||
const kvUri: string = `https://${keyVaultName}.vault.azure.net`;
|
||||
const credential: DefaultAzureCredential = new DefaultAzureCredential();
|
||||
const client: SecretClient = new SecretClient(kvUri, credential);
|
||||
const keyVaultName: string = process.env["KEY_VAULT_NAME"]
|
||||
if (!keyVaultName) throw new Error("KEY_VAULT_NAME is empty")
|
||||
const kvUri: string = `https://${keyVaultName}.vault.azure.net`
|
||||
const credential: DefaultAzureCredential = new DefaultAzureCredential()
|
||||
const client: SecretClient = new SecretClient(kvUri, credential)
|
||||
|
||||
async function getSecret(secretName: string): Promise<string> {
|
||||
const secret: KeyVaultSecret = await client.getSecret(secretName);
|
||||
return secret.value;
|
||||
const secret: KeyVaultSecret = await client.getSecret(secretName)
|
||||
return secret.value
|
||||
}
|
||||
|
||||
export async function getGithubAppPrivateKey(): Promise<string> {
|
||||
return await getSecret("github-app-private-key-0");
|
||||
return await getSecret("github-app-private-key-0")
|
||||
}
|
||||
|
||||
export async function getGithubAppWebhookSecret(): Promise<string> {
|
||||
return await getSecret("github-app-webhook-secret-1");
|
||||
return await getSecret("github-app-webhook-secret-1")
|
||||
}
|
||||
|
||||
export async function getCFAPIJWTPrivateKey(): Promise<string> {
|
||||
return await getSecret("cf-api-jwt-private-key-0");
|
||||
return await getSecret("cf-api-jwt-private-key-0")
|
||||
}
|
||||
|
||||
export async function getCFAPIJWTPublicKey(): Promise<string> {
|
||||
return await getSecret("cf-api-jwt-public-key-0");
|
||||
return await getSecret("cf-api-jwt-public-key-0")
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,14 +5,12 @@
|
|||
// import { PrismaClient, type cf_api_keys } from 'common'
|
||||
//
|
||||
|
||||
|
||||
// TODO reenable this once
|
||||
// https://github.com/vercel/next.js/issues/46070
|
||||
// is resolved.
|
||||
// export * from '@prisma/client';
|
||||
|
||||
import {PrismaClient} from '@prisma/client';
|
||||
import { PrismaClient } from "@prisma/client"
|
||||
|
||||
export const prisma = new PrismaClient();
|
||||
export const prisma = new PrismaClient()
|
||||
prisma.cf_api_keys
|
||||
|
||||
|
|
|
|||
|
|
@ -1,33 +1,34 @@
|
|||
import * as jose from 'jose';
|
||||
import crypto from 'crypto';
|
||||
import {getCFAPIJWTPrivateKey, getCFAPIJWTPublicKey} from './azure-keyvault';
|
||||
import * as jose from "jose"
|
||||
import crypto from "crypto"
|
||||
import { getCFAPIJWTPrivateKey, getCFAPIJWTPublicKey } from "./azure-keyvault"
|
||||
|
||||
export async function getJWTToken (cf_user_id: string) {
|
||||
export async function getJWTToken(cf_user_id: string) {
|
||||
const private_secret_key = await getCFAPIJWTPrivateKey()
|
||||
const secret = crypto.createPrivateKey(private_secret_key)
|
||||
const alg = 'EdDSA'
|
||||
const alg = "EdDSA"
|
||||
|
||||
const jwt :string = await new jose.SignJWT({ 'user_id': cf_user_id })
|
||||
const jwt: string = await new jose.SignJWT({ user_id: cf_user_id })
|
||||
.setProtectedHeader({ alg })
|
||||
.setIssuedAt()
|
||||
.setIssuer('https://codeflash.ai')
|
||||
.setExpirationTime('30d')
|
||||
.setIssuer("https://codeflash.ai")
|
||||
.setExpirationTime("30d")
|
||||
.sign(secret)
|
||||
|
||||
return jwt
|
||||
}
|
||||
|
||||
export async function verifyJWTToken (jwt: string) {
|
||||
export async function verifyJWTToken(jwt: string) {
|
||||
const public_secret_key = await getCFAPIJWTPublicKey()
|
||||
const alg = 'EdDSA'
|
||||
const alg = "EdDSA"
|
||||
const publicKey = await jose.importSPKI(public_secret_key, alg)
|
||||
|
||||
const { payload, protectedHeader } = await jose.jwtVerify(jwt, publicKey, {
|
||||
issuer: 'https://codeflash.ai',
|
||||
}).catch(async (err) => {
|
||||
console.log(err)
|
||||
throw new jose.errors.JWSSignatureVerificationFailed()
|
||||
})
|
||||
return {payload, protectedHeader}
|
||||
const { payload, protectedHeader } = await jose
|
||||
.jwtVerify(jwt, publicKey, {
|
||||
issuer: "https://codeflash.ai",
|
||||
})
|
||||
.catch(async (err) => {
|
||||
console.log(err)
|
||||
throw new jose.errors.JWSSignatureVerificationFailed()
|
||||
})
|
||||
return { payload, protectedHeader }
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,74 +1,73 @@
|
|||
'use server'
|
||||
import util from 'util'
|
||||
import {createHash, type Hash, randomBytes} from 'crypto'
|
||||
import {prisma} from "./index";
|
||||
"use server"
|
||||
import util from "util"
|
||||
import { createHash, type Hash, randomBytes } from "crypto"
|
||||
import { prisma } from "./index"
|
||||
|
||||
// const prisma = new PrismaClient()
|
||||
async function generateRandomAPIToken(): Promise<string> {
|
||||
const randomBytesAsync = util.promisify(randomBytes)
|
||||
const token = (await randomBytesAsync(48)).toString('base64url')
|
||||
return 'cf-' + token
|
||||
const randomBytesAsync = util.promisify(randomBytes)
|
||||
const token = (await randomBytesAsync(48)).toString("base64url")
|
||||
return "cf-" + token
|
||||
}
|
||||
|
||||
export async function hashToken(token: string) {
|
||||
const hash: Hash = createHash('sha384')
|
||||
hash.update(token)
|
||||
return hash.digest('base64url');
|
||||
const hash: Hash = createHash("sha384")
|
||||
hash.update(token)
|
||||
return hash.digest("base64url")
|
||||
}
|
||||
|
||||
export async function genAndStoreAPITokenHash(keyName: string, userId: number): Promise<string> {
|
||||
// The idea here is to not store the API Keys in the database in plaintext. Because if we do that, then
|
||||
// the keys are vulnerable to internal and external database breaches. Instead, we store the hash
|
||||
// of the key in the database. When a user wants to use the key, they send the plaintext key to the server,
|
||||
// which hashes it and compares it to the hash in the database. If they match, then the key is valid.
|
||||
// The reason to not use bcrypt is that bcrypt is designed to be slow, which is good for passwords, but
|
||||
// not good for API keys. Also with salting, one can't simply search for a key in the DB.
|
||||
// Instead, we use SHA-384, which is designed to be fast. Rainbow table attacks are mitigated by using a long
|
||||
// random key, for which rainbow tables become unfeasible and the attacker would have to brute force the key.
|
||||
// The reason to use SHA-384 instead of SHA-256 is that SHA-384 has a longer output, which makes it much harder
|
||||
// to brute force.
|
||||
// The idea here is to not store the API Keys in the database in plaintext. Because if we do that, then
|
||||
// the keys are vulnerable to internal and external database breaches. Instead, we store the hash
|
||||
// of the key in the database. When a user wants to use the key, they send the plaintext key to the server,
|
||||
// which hashes it and compares it to the hash in the database. If they match, then the key is valid.
|
||||
// The reason to not use bcrypt is that bcrypt is designed to be slow, which is good for passwords, but
|
||||
// not good for API keys. Also with salting, one can't simply search for a key in the DB.
|
||||
// Instead, we use SHA-384, which is designed to be fast. Rainbow table attacks are mitigated by using a long
|
||||
// random key, for which rainbow tables become unfeasible and the attacker would have to brute force the key.
|
||||
// The reason to use SHA-384 instead of SHA-256 is that SHA-384 has a longer output, which makes it much harder
|
||||
// to brute force.
|
||||
|
||||
const token: string = await generateRandomAPIToken()
|
||||
const hashedToken = await hashToken(token);
|
||||
await prisma.cf_api_keys.create({
|
||||
data: {
|
||||
key: hashedToken,
|
||||
user_id: userId,
|
||||
suffix: token.slice(-4),
|
||||
name: keyName
|
||||
}
|
||||
})
|
||||
return token
|
||||
const token: string = await generateRandomAPIToken()
|
||||
const hashedToken = await hashToken(token)
|
||||
await prisma.cf_api_keys.create({
|
||||
data: {
|
||||
key: hashedToken,
|
||||
user_id: userId,
|
||||
suffix: token.slice(-4),
|
||||
name: keyName,
|
||||
},
|
||||
})
|
||||
return token
|
||||
}
|
||||
|
||||
export async function userForAPIKey(key: string): Promise<null | string> {
|
||||
// TODO: Add a rate limiter to prevent brute force attacks.
|
||||
const hashedToken = await hashToken(key);
|
||||
try {
|
||||
const result = await prisma.cf_api_keys.update({
|
||||
where: {
|
||||
key: hashedToken
|
||||
},
|
||||
data: {
|
||||
last_used: new Date()
|
||||
}
|
||||
})
|
||||
if(result == null){
|
||||
return null;
|
||||
}
|
||||
else{
|
||||
return result.user_id
|
||||
}
|
||||
} catch (e) {
|
||||
return null;
|
||||
// TODO: Add a rate limiter to prevent brute force attacks.
|
||||
const hashedToken = await hashToken(key)
|
||||
try {
|
||||
const result = await prisma.cf_api_keys.update({
|
||||
where: {
|
||||
key: hashedToken,
|
||||
},
|
||||
data: {
|
||||
last_used: new Date(),
|
||||
},
|
||||
})
|
||||
if (result == null) {
|
||||
return null
|
||||
} else {
|
||||
return result.user_id
|
||||
}
|
||||
} catch (e) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export async function deleteAPIKeyById(id: number, userId: number): void {
|
||||
await prisma.cf_api_keys.delete({
|
||||
where: {
|
||||
id,
|
||||
user_id: userId
|
||||
}
|
||||
})
|
||||
await prisma.cf_api_keys.delete({
|
||||
where: {
|
||||
id,
|
||||
user_id: userId,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue