diff --git a/apps/portal/package.json b/apps/portal/package.json index 208b1940..a8a184d8 100644 --- a/apps/portal/package.json +++ b/apps/portal/package.json @@ -38,6 +38,7 @@ "react-popper-tooltip": "^4.4.2", "react-query": "^3.39.2", "superjson": "^1.10.0", + "unique-names-generator": "^4.7.1", "zod": "^3.18.0" }, "devDependencies": { diff --git a/apps/portal/prisma/migrations/20221024123252_add_upvotes_to_schema/migration.sql b/apps/portal/prisma/migrations/20221024123252_add_upvotes_to_schema/migration.sql new file mode 100644 index 00000000..81d336ec --- /dev/null +++ b/apps/portal/prisma/migrations/20221024123252_add_upvotes_to_schema/migration.sql @@ -0,0 +1,14 @@ +/* + Warnings: + + - Added the required column `upvotes` to the `QuestionsAnswerComment` table without a default value. This is not possible if the table is not empty. + +*/ +-- AlterTable +ALTER TABLE "QuestionsAnswer" ADD COLUMN "upvotes" INTEGER NOT NULL DEFAULT 0; + +-- AlterTable +ALTER TABLE "QuestionsAnswerComment" ADD COLUMN "upvotes" INTEGER NOT NULL; + +-- AlterTable +ALTER TABLE "QuestionsQuestionComment" ADD COLUMN "upvotes" INTEGER NOT NULL DEFAULT 0; diff --git a/apps/portal/prisma/migrations/20221024123849_add_upvotes_default_value/migration.sql b/apps/portal/prisma/migrations/20221024123849_add_upvotes_default_value/migration.sql new file mode 100644 index 00000000..f4a342af --- /dev/null +++ b/apps/portal/prisma/migrations/20221024123849_add_upvotes_default_value/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "QuestionsAnswerComment" ALTER COLUMN "upvotes" SET DEFAULT 0; diff --git a/apps/portal/prisma/schema.prisma b/apps/portal/prisma/schema.prisma index 2b4aec91..42f1b974 100644 --- a/apps/portal/prisma/schema.prisma +++ b/apps/portal/prisma/schema.prisma @@ -457,6 +457,7 @@ model QuestionsQuestionComment { id String @id @default(cuid()) questionId String userId String? + upvotes Int @default(0) content String @db.Text createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@ -485,6 +486,7 @@ model QuestionsAnswer { questionId String userId String? content String @db.Text + upvotes Int @default(0) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@ -513,6 +515,7 @@ model QuestionsAnswerComment { answerId String userId String? content String @db.Text + upvotes Int @default(0) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt diff --git a/apps/portal/src/components/global/AppShell.tsx b/apps/portal/src/components/global/AppShell.tsx index bcb9af67..a458a433 100644 --- a/apps/portal/src/components/global/AppShell.tsx +++ b/apps/portal/src/components/global/AppShell.tsx @@ -85,8 +85,8 @@ function ProfileJewel() { {({ active }) => ( @@ -178,9 +178,9 @@ export default function AppShell({ children }: Props) { {/* Content area */}
{label}
{'>'}
{description}
We value your privacy.
+
To keep you offer profile strictly anonymous, only people who have the link below can edit it.
Link copied to clipboard!
+ {/*
If you do not want to keep the edit link, you can opt to save this profile under your user account. It will still only be editable by you. @@ -83,7 +81,7 @@ export default function OffersProfileSave({ variant="primary" onClick={saveProfile} /> -
{JSON.stringify(formMethods.watch(), null, 2)}
{`${startDate || 'N/A'} - ${endDate || 'N/A'}`}
{receivedMonth}
{`${duration} months`}
Base / year: {base} ⋅ Stocks / year: {stocks} ⋅ Bonus / year:{' '} {bonus} diff --git a/apps/portal/src/components/offers/profile/ProfileComments.tsx b/apps/portal/src/components/offers/profile/ProfileComments.tsx index d30645a8..b26f78ae 100644 --- a/apps/portal/src/components/offers/profile/ProfileComments.tsx +++ b/apps/portal/src/components/offers/profile/ProfileComments.tsx @@ -1,7 +1,13 @@ import { signIn, useSession } from 'next-auth/react'; import { useState } from 'react'; import { ClipboardDocumentIcon, ShareIcon } from '@heroicons/react/24/outline'; -import { Button, HorizontalDivider, Spinner, TextArea } from '@tih/ui'; +import { + Button, + HorizontalDivider, + Spinner, + TextArea, + useToast, +} from '@tih/ui'; import ExpandableCommentCard from '~/components/offers/profile/comments/ExpandableCommentCard'; @@ -30,6 +36,7 @@ export default function ProfileComments({ const { data: session, status } = useSession(); const [currentReply, setCurrentReply] = useState(''); const [replies, setReplies] = useState>(); + const { showToast } = useToast(); const commentsQuery = trpc.useQuery( ['offers.comments.getComments', { profileId }], @@ -51,6 +58,10 @@ export default function ProfileComments({ }); function handleComment(message: string) { + if (!currentReply.length) { + return; + } + if (isEditable) { // If it is with edit permission, send comment to API with username = null createCommentMutation.mutate( @@ -104,7 +115,13 @@ export default function ProfileComments({ label="Copy profile edit link" size="sm" variant="secondary" - onClick={() => copyProfileLink(profileId, token)} + onClick={() => { + copyProfileLink(profileId, token); + showToast({ + title: `Profile edit link copied to clipboard!`, + variant: 'success', + }); + }} /> )} copyProfileLink(profileId)} + onClick={() => { + copyProfileLink(profileId); + showToast({ + title: `Public profile link copied to clipboard!`, + variant: 'success', + }); + }} />
{title}
{comment.user.name ?? 'Reviewer ABC'}
{isCommentOwner ? '(Me)' : ''}
Please fill in at least one section to submit your review
It's free! Take charge of your resume game by learning from the top engineers in the field.
{isExpanded ? 'See Less' : 'See More'}
{ event.preventDefault(); diff --git a/apps/portal/src/components/shared/CompaniesTypeahead.tsx b/apps/portal/src/components/shared/CompaniesTypeahead.tsx index 2eacc357..ffc90f52 100644 --- a/apps/portal/src/components/shared/CompaniesTypeahead.tsx +++ b/apps/portal/src/components/shared/CompaniesTypeahead.tsx @@ -9,6 +9,7 @@ type Props = Readonly<{ isLabelHidden?: boolean; onSelect: (option: TypeaheadOption) => void; placeHolder?: string; + required?: boolean; }>; export default function CompaniesTypeahead({ @@ -16,6 +17,7 @@ export default function CompaniesTypeahead({ onSelect, isLabelHidden, placeHolder, + required, }: Props) { const [query, setQuery] = useState(''); const companies = trpc.useQuery([ @@ -42,6 +44,7 @@ export default function CompaniesTypeahead({ })) ?? [] } placeholder={placeHolder} + required={required} onQueryChange={setQuery} onSelect={onSelect} /> diff --git a/apps/portal/src/components/shared/JobTitles.ts b/apps/portal/src/components/shared/JobTitles.ts new file mode 100644 index 00000000..0dfcb336 --- /dev/null +++ b/apps/portal/src/components/shared/JobTitles.ts @@ -0,0 +1,31 @@ +export const JobTitleLabels = { + 'ai-ml-engineer': 'AI/ML Engineer', + 'algorithms-engineer': 'Algorithms Engineer', + 'android-engineer': 'Android Software Engineer', + 'applications-engineer': 'Applications Engineer', + 'back-end-engineer': 'Back End Engineer', + 'business-engineer': 'Business Engineer', + 'data-engineer': 'Data Engineer', + 'devops-engineer': 'DevOps Engineer', + 'enterprise-engineer': 'Enterprise Engineer', + 'front-end-engineer': 'Front End Engineer', + 'hardware-engineer': 'Hardware Engineer', + 'ios-engineer': 'iOS Software Engineer', + 'mobile-engineer': 'Mobile Software Engineer (iOS + Android)', + 'networks-engineer': 'Networks Engineer', + 'partner-engineer': 'Partner Engineer', + 'production-engineer': 'Production Engineer', + 'research-engineer': 'Research Engineer', + 'sales-engineer': 'Sales Engineer', + 'security-engineer': 'Security Engineer', + 'site-reliability-engineer': 'Site Reliability Engineer (SRE)', + 'software-engineer': 'Software Engineer', + 'systems-engineer': 'Systems Engineer', + 'test-engineer': 'QA/Test Engineer (SDET)', +}; + +export type JobTitleType = keyof typeof JobTitleLabels; + +export function getLabelForJobTitleType(jobTitle: JobTitleType): string { + return JobTitleLabels[jobTitle]; +} diff --git a/apps/portal/src/components/shared/JobTitlesTypahead.tsx b/apps/portal/src/components/shared/JobTitlesTypahead.tsx new file mode 100644 index 00000000..036af8f1 --- /dev/null +++ b/apps/portal/src/components/shared/JobTitlesTypahead.tsx @@ -0,0 +1,48 @@ +import { useState } from 'react'; +import type { TypeaheadOption } from '@tih/ui'; +import { Typeahead } from '@tih/ui'; + +import { JobTitleLabels } from './JobTitles'; + +type Props = Readonly<{ + disabled?: boolean; + isLabelHidden?: boolean; + onSelect: (option: TypeaheadOption) => void; + placeHolder?: string; + required?: boolean; +}>; + +export default function JobTitlesTypeahead({ + disabled, + onSelect, + isLabelHidden, + placeHolder, + required, +}: Props) { + const [query, setQuery] = useState(''); + const options = Object.entries(JobTitleLabels) + .map(([slug, label]) => ({ + id: slug, + label, + value: slug, + })) + .filter( + ({ label }) => + label.toLocaleLowerCase().indexOf(query.toLocaleLowerCase()) > -1, + ); + + return ( + + ); +} diff --git a/apps/portal/src/pages/_document.tsx b/apps/portal/src/pages/_document.tsx index 2054857e..69112ab2 100644 --- a/apps/portal/src/pages/_document.tsx +++ b/apps/portal/src/pages/_document.tsx @@ -2,7 +2,7 @@ import { Head, Html, Main, NextScript } from 'next/document'; export default function Document() { return ( - +
+ Analyze your offers using profiles from fellow software engineers. +
+ All offer profiles are anonymized and we do not store information + about your personal identity. +
+ {feature.description} +
Nothing found.
- Upload resume (PDF format) - - {' '} - * - -
- {resumeFile.name} -
+ Upload resume (PDF format) + + {' '} + * + +
+ {resumeFile.name} +
+ PDF up to {FILE_SIZE_LIMIT_MB}MB +
- PDF up to {FILE_SIZE_LIMIT_MB}MB -
{fileUploadError}
{JSON.stringify(selectedCompany, null, 2)}
{JSON.stringify(selectedJobTitle, null, 2)}
A list of all Todos added by everyone.
+ {description} +
+ {errorMessage} +